1(function($) { 2 class SlickZoom { 3 constructor(sliderSelector) { 4 this.slider = $(sliderSelector); 5 if (this.slider.length === 0) return; 6 7 this._initStyles(); 8 this._initVariables(); 9 this._bindEvents(); 10 this._startAnimationLoop(); 11 } 12 13 _initStyles() { 14 const style = document.createElement('style'); 15 style.textContent = ` 16 .slick-slider { 17 touch-action: auto !important; 18 user-select: none; 19 -webkit-user-drag: none; 20 -webkit-touch-callout: none; 21 overscroll-behavior: contain; 22 } 23 .slick-slide img { 24 will-change: transform; 25 -webkit-user-select: none; 26 -webkit-user-drag: none; 27 pointer-events: none; 28 } 29 `; 30 document.head.appendChild(style); 31 } 32 33 _initVariables() { 34 this.scale = 1; 35 this.targetScale = 1; 36 37 this.translateX = 0; 38 this.translateY = 0; 39 this.targetX = 0; 40 this.targetY = 0; 41 42 this.initialDistance = null; 43 this.isPanning = false; 44 this.startX = 0; 45 this.startY = 0; 46 this.isZoomActive = false; 47 } 48 49 _bindEvents() { 50 const sliderEl = this.slider[0]; 51 52 sliderEl.addEventListener('touchstart', (e) => this._handleTouchStart(e), { passive: false }); 53 sliderEl.addEventListener('touchmove', (e) => this._handleTouchMove(e), { passive: false }); 54 sliderEl.addEventListener('touchend', (e) => this._handleTouchEnd(e), { passive: false }); 55 56 this.slider.on('beforeChange', (event, slick, currentSlide, nextSlide, changeType) => { 57 if (this.isZoomActive) { 58 if (changeType === 'swipe') { 59 event.preventDefault(); 60 event.stopPropagation(); 61 slick.pauseAuto(); 62 return false; 63 } else { 64 this._resetZoom(); 65 } 66 } 67 }); 68 } 69 70 _disableSwipe() { 71 this.slider.slick('slickSetOption', 'swipe', false, true); 72 this.slider.slick('slickSetOption', 'touchMove', false, true); 73 } 74 75 _enableSwipe() { 76 this.slider.slick('slickSetOption', 'swipe', true, true); 77 this.slider.slick('slickSetOption', 'touchMove', true, true); 78 } 79 80 _handleTouchStart(event) { 81 const touches = event.touches; 82 83 if (touches.length === 2) { 84 event.preventDefault(); 85 this.initialDistance = this._getTouchDistance(touches[0], touches[1]); 86 this._disableSwipe(); 87 this.isZoomActive = true; 88 } else if (touches.length === 1 && this.scale > 1) { 89 event.preventDefault(); 90 this.isPanning = true; 91 this.startX = touches[0].clientX - this.targetX; 92 this.startY = touches[0].clientY - this.targetY; 93 } 94 } 95 96 _handleTouchMove(event) { 97 const touches = event.touches; 98 99 if (touches.length === 2 && this.initialDistance !== null) { 100 event.preventDefault(); 101 const currentDistance = this._getTouchDistance(touches[0], touches[1]); 102 const newScale = Math.max(1, Math.min(this.scale * (currentDistance / this.initialDistance), 3)); 103 this.targetScale = newScale; 104 } else if (this.isPanning && this.scale > 1) { 105 event.preventDefault(); 106 this.targetX = touches[0].clientX - this.startX; 107 this.targetY = touches[0].clientY - this.startY; 108 this._adjustTranslation(this.targetScale); 109 } 110 } 111 112 _handleTouchEnd() { 113 this.initialDistance = null; 114 this.isPanning = false;
115 116 if (Math.abs(this.targetScale - 1) < 0.01) { 117 this._resetZoom(); 118 } else { 119 this.isZoomActive = true; 120 this._disableSwipe(); 121 } 122 } 123 124 _getTouchDistance(touch1, touch2) { 125 const dx = touch2.clientX - touch1.clientX; 126 const dy = touch2.clientY - touch1.clientY; 127 return Math.sqrt(dx * dx + dy * dy); 128 } 129 130 _applyTransform() { 131 const activeSlide = this.slider.find('.slick-active'); 132 const image = activeSlide.find('img'); 133 image.css('transform', `translate(${this.translateX}px, ${this.translateY}px) scale(${this.scale})`); 134 } 135 136 _calculateBounds(imageElement, scale) { 137 const rect = imageElement.getBoundingClientRect(); 138 const width = rect.width * scale; 139 const height = rect.height * scale; 140 141 return { 142 left: -(width - window.innerWidth) / 2, 143 right: (width - window.innerWidth) / 2, 144 top: -(height - window.innerHeight) / 2, 145 bottom: (height - window.innerHeight) / 2 146 }; 147 } 148 149 _adjustTranslation(newScale) { 150 const activeSlide = this.slider.find('.slick-active'); 151 const image = activeSlide.find('img')[0]; 152 const bounds = this._calculateBounds(image, newScale); 153 154 this.targetX = Math.max(bounds.left, Math.min(bounds.right, this.targetX)); 155 this.targetY = Math.max(bounds.top, Math.min(bounds.bottom, this.targetY)); 156 157 if (Math.abs(newScale - 1) < 0.01) { 158 this.targetX = 0; 159 this.targetY = 0; 160 } 161 } 162 163 _resetZoom() { 164 this.scale = 1; 165 this.targetScale = 1; 166 this.translateX = 0; 167 this.translateY = 0; 168 this.targetX = 0; 169 this.targetY = 0; 170 this.isZoomActive = false; 171 this._applyTransform(); 172 this._enableSwipe(); 173 } 174 175 _startAnimationLoop() { 176 const animate = () => { 177 this.scale += (this.targetScale - this.scale) * 0.2; 178 this.translateX += (this.targetX - this.translateX) * 0.2; 179 this.translateY += (this.targetY - this.translateY) * 0.2; 180 181 this._applyTransform(); 182 requestAnimationFrame(animate); 183 }; 184 animate(); 185 } 186 } 187 188 $(document).ready(function() { 189 new SlickZoom(zoomSettings.containerSelector); 190 }); 191})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.