/** * zoom-controller-enhanced.ts — Advanced zoom controller with smooth transitions * * Provides smooth zoom animations, adaptive quality during zoom, * and intelligent zoom level management. */ class EnhancedZoomController { constructor(container, pageManager, onScaleChange, getVisibilityState, zoomLabel) { this.container = container; this.pageManager = pageManager; this.onScaleChange = onScaleChange; this.getVisibilityState = getVisibilityState; this.zoomLabel = zoomLabel; // Zoom state this.scale = 1.0; this.targetScale = 1.0; this.minScale = 0.1; this.maxScale = 5.0; this.zoomAnimation = null; this.isZooming = false; this.zoomQuality = 1.0; // Setup event listeners this.setupEventListeners(); // Update zoom label this.updateZoomLabel(); } setupEventListeners() { // Smooth zoom with animation this.container.addEventListener('wheel', (event) => { if (event.ctrlKey || event.metaKey) { this.handleZoomWheel(event); event.preventDefault(); } }, { passive: false }); // Double-click for zoom to fit this.container.addEventListener('dblclick', (event) => { this.zoomToFit(); }); } handleZoomWheel(event) { // Calculate zoom factor based on wheel delta const zoomFactor = event.deltaY > 0 ? 0.9 : 1.1; const newScale = this.scale * zoomFactor; // Apply constraints const constrainedScale = Math.max(this.minScale, Math.min(this.maxScale, newScale)); if (Math.abs(constrainedScale - this.scale) > 0.01) { this.applyScale(constrainedScale); } } applyScale(newScale, options = {}) { const { animate = true, qualityOverride = null } = options; // Constrain scale this.targetScale = Math.max(this.minScale, Math.min(this.maxScale, newScale)); if (Math.abs(this.targetScale - this.scale) < 0.01) return; if (animate) { this.startZoomAnimation(qualityOverride); } else { this.scale = this.targetScale; const { visibleSet, bufferSet } = this.getVisibilityState(); this.onScaleChange(this.scale, visibleSet, bufferSet); this.updateZoomLabel(); } } startZoomAnimation(qualityOverride = null) { if (this.zoomAnimation) { cancelAnimationFrame(this.zoomAnimation); } this.isZooming = true; const startScale = this.scale; const endScale = this.targetScale; const startTime = performance.now(); const duration = 150; // 150ms for smooth zoom // Set lower quality during zoom for performance this.zoomQuality = qualityOverride !== null ? qualityOverride : 0.8; const animate = (currentTime) => { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // Ease-in-out animation for natural feel const easedProgress = this.easeInOutCubic(progress); this.scale = startScale + (endScale - startScale) * easedProgress; // Apply the scale this.applyScaleToContainer(); // Notify about scale change with current quality const { visibleSet, bufferSet } = this.getVisibilityState(); this.onScaleChange(this.scale, visibleSet, bufferSet); if (progress < 1) { this.zoomAnimation = requestAnimationFrame(animate); } else { this.isZooming = false; this.scale = endScale; this.zoomQuality = 1.0; // Restore full quality // Final high-quality render const { visibleSet, bufferSet } = this.getVisibilityState(); this.onScaleChange(this.scale, visibleSet, bufferSet); this.updateZoomLabel(); this.zoomAnimation = null; } }; this.zoomAnimation = requestAnimationFrame(animate); } applyScaleToContainer() { // Apply CSS transform for smooth zooming this.container.style.transform = `scale(${this.scale})`; // Also update the page manager if (this.pageManager.setScale) { this.pageManager.setScale(this.scale); } } easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } zoomToFit() { if (!this.pageManager || !this.pageManager.viewports) return; const firstViewport = this.pageManager.viewports[0]; if (!firstViewport) return; const containerWidth = this.container.clientWidth; const containerHeight = this.container.clientHeight; // Calculate fit scale const scaleW = (containerWidth - 40) / firstViewport.width; const scaleH = (containerHeight - 40) / firstViewport.height; const fitScale = Math.min(scaleW, scaleH); this.applyScale(Math.max(this.minScale, Math.min(this.maxScale, fitScale))); } zoomIn() { this.applyScale(this.scale * 1.25); } zoomOut() { this.applyScale(this.scale * 0.8); } zoomToActualSize() { this.applyScale(1.0); } getCurrentScale() { return this.scale; } getCurrentZoomQuality() { return this.zoomQuality; } isZoomingInProgress() { return this.isZooming; } updateZoomLabel() { if (this.zoomLabel) { this.zoomLabel.textContent = `${Math.round(this.scale * 100)}%`; } } cleanup() { if (this.zoomAnimation) { cancelAnimationFrame(this.zoomAnimation); } } getZoomStats() { return { currentScale: this.scale, targetScale: this.targetScale, isZooming: this.isZooming, zoomQuality: this.zoomQuality }; } } // Export for ES6 modules export { EnhancedZoomController }; //# sourceMappingURL=zoom-controller-enhanced.js.map