diff --git a/core/src/components/slides/swiper/swiper.bundle.js b/core/src/components/slides/swiper/swiper.bundle.js index 010d60656fb..e8b4babd340 100644 --- a/core/src/components/slides/swiper/swiper.bundle.js +++ b/core/src/components/slides/swiper/swiper.bundle.js @@ -4939,6 +4939,512 @@ var scrollbar = { }, }; +const Zoom = { + // Calc Scale From Multi-touches + getDistanceBetweenTouches(e) { + if (e.targetTouches.length < 2) return 1; + const x1 = e.targetTouches[0].pageX; + const y1 = e.targetTouches[0].pageY; + const x2 = e.targetTouches[1].pageX; + const y2 = e.targetTouches[1].pageY; + const distance = Math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2)); + return distance; + }, + // Events + onGestureStart(e) { + const swiper = this; + const params = swiper.params.zoom; + const zoom = swiper.zoom; + const { gesture } = zoom; + zoom.fakeGestureTouched = false; + zoom.fakeGestureMoved = false; + if (!Support.gestures) { + if (e.type !== 'touchstart' || (e.type === 'touchstart' && e.targetTouches.length < 2)) { + return; + } + zoom.fakeGestureTouched = true; + gesture.scaleStart = Zoom.getDistanceBetweenTouches(e); + } + if (!gesture.$slideEl || !gesture.$slideEl.length) { + gesture.$slideEl = $(e.target).closest('.swiper-slide'); + if (gesture.$slideEl.length === 0) gesture.$slideEl = swiper.slides.eq(swiper.activeIndex); + gesture.$imageEl = gesture.$slideEl.find('img, svg, canvas'); + gesture.$imageWrapEl = gesture.$imageEl.parent(`.${params.containerClass}`); + gesture.maxRatio = gesture.$imageWrapEl.attr('data-swiper-zoom') || params.maxRatio; + if (gesture.$imageWrapEl.length === 0) { + gesture.$imageEl = undefined; + return; + } + } + gesture.$imageEl.transition(0); + swiper.zoom.isScaling = true; + }, + onGestureChange(e) { + const swiper = this; + const params = swiper.params.zoom; + const zoom = swiper.zoom; + const { gesture } = zoom; + if (!Support.gestures) { + if (e.type !== 'touchmove' || (e.type === 'touchmove' && e.targetTouches.length < 2)) { + return; + } + zoom.fakeGestureMoved = true; + gesture.scaleMove = Zoom.getDistanceBetweenTouches(e); + } + if (!gesture.$imageEl || gesture.$imageEl.length === 0) return; + if (Support.gestures) { + swiper.zoom.scale = e.scale * zoom.currentScale; + } else { + zoom.scale = (gesture.scaleMove / gesture.scaleStart) * zoom.currentScale; + } + if (zoom.scale > gesture.maxRatio) { + zoom.scale = (gesture.maxRatio - 1) + (((zoom.scale - gesture.maxRatio) + 1) ** 0.5); + } + if (zoom.scale < params.minRatio) { + zoom.scale = (params.minRatio + 1) - (((params.minRatio - zoom.scale) + 1) ** 0.5); + } + gesture.$imageEl.transform(`translate3d(0,0,0) scale(${zoom.scale})`); + }, + onGestureEnd(e) { + const swiper = this; + const params = swiper.params.zoom; + const zoom = swiper.zoom; + const { gesture } = zoom; + if (!Support.gestures) { + if (!zoom.fakeGestureTouched || !zoom.fakeGestureMoved) { + return; + } + if (e.type !== 'touchend' || (e.type === 'touchend' && e.changedTouches.length < 2 && !Device.android)) { + return; + } + zoom.fakeGestureTouched = false; + zoom.fakeGestureMoved = false; + } + if (!gesture.$imageEl || gesture.$imageEl.length === 0) return; + zoom.scale = Math.max(Math.min(zoom.scale, gesture.maxRatio), params.minRatio); + gesture.$imageEl.transition(swiper.params.speed).transform(`translate3d(0,0,0) scale(${zoom.scale})`); + zoom.currentScale = zoom.scale; + zoom.isScaling = false; + if (zoom.scale === 1) gesture.$slideEl = undefined; + }, + onTouchStart(e) { + const swiper = this; + const zoom = swiper.zoom; + const { gesture, image } = zoom; + if (!gesture.$imageEl || gesture.$imageEl.length === 0) return; + if (image.isTouched) return; + if (Device.android) e.preventDefault(); + image.isTouched = true; + image.touchesStart.x = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX; + image.touchesStart.y = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY; + }, + onTouchMove(e) { + const swiper = this; + const zoom = swiper.zoom; + const { gesture, image, velocity } = zoom; + if (!gesture.$imageEl || gesture.$imageEl.length === 0) return; + swiper.allowClick = false; + if (!image.isTouched || !gesture.$slideEl) return; + + if (!image.isMoved) { + image.width = gesture.$imageEl[0].offsetWidth; + image.height = gesture.$imageEl[0].offsetHeight; + image.startX = Utils.getTranslate(gesture.$imageWrapEl[0], 'x') || 0; + image.startY = Utils.getTranslate(gesture.$imageWrapEl[0], 'y') || 0; + gesture.slideWidth = gesture.$slideEl[0].offsetWidth; + gesture.slideHeight = gesture.$slideEl[0].offsetHeight; + gesture.$imageWrapEl.transition(0); + if (swiper.rtl) { + image.startX = -image.startX; + image.startY = -image.startY; + } + } + // Define if we need image drag + const scaledWidth = image.width * zoom.scale; + const scaledHeight = image.height * zoom.scale; + + if (scaledWidth < gesture.slideWidth && scaledHeight < gesture.slideHeight) return; + + image.minX = Math.min(((gesture.slideWidth / 2) - (scaledWidth / 2)), 0); + image.maxX = -image.minX; + image.minY = Math.min(((gesture.slideHeight / 2) - (scaledHeight / 2)), 0); + image.maxY = -image.minY; + + image.touchesCurrent.x = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX; + image.touchesCurrent.y = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY; + + if (!image.isMoved && !zoom.isScaling) { + if ( + swiper.isHorizontal() + && ( + (Math.floor(image.minX) === Math.floor(image.startX) && image.touchesCurrent.x < image.touchesStart.x) + || (Math.floor(image.maxX) === Math.floor(image.startX) && image.touchesCurrent.x > image.touchesStart.x) + ) + ) { + image.isTouched = false; + return; + } if ( + !swiper.isHorizontal() + && ( + (Math.floor(image.minY) === Math.floor(image.startY) && image.touchesCurrent.y < image.touchesStart.y) + || (Math.floor(image.maxY) === Math.floor(image.startY) && image.touchesCurrent.y > image.touchesStart.y) + ) + ) { + image.isTouched = false; + return; + } + } + e.preventDefault(); + e.stopPropagation(); + + image.isMoved = true; + image.currentX = (image.touchesCurrent.x - image.touchesStart.x) + image.startX; + image.currentY = (image.touchesCurrent.y - image.touchesStart.y) + image.startY; + + if (image.currentX < image.minX) { + image.currentX = (image.minX + 1) - (((image.minX - image.currentX) + 1) ** 0.8); + } + if (image.currentX > image.maxX) { + image.currentX = (image.maxX - 1) + (((image.currentX - image.maxX) + 1) ** 0.8); + } + + if (image.currentY < image.minY) { + image.currentY = (image.minY + 1) - (((image.minY - image.currentY) + 1) ** 0.8); + } + if (image.currentY > image.maxY) { + image.currentY = (image.maxY - 1) + (((image.currentY - image.maxY) + 1) ** 0.8); + } + + // Velocity + if (!velocity.prevPositionX) velocity.prevPositionX = image.touchesCurrent.x; + if (!velocity.prevPositionY) velocity.prevPositionY = image.touchesCurrent.y; + if (!velocity.prevTime) velocity.prevTime = Date.now(); + velocity.x = (image.touchesCurrent.x - velocity.prevPositionX) / (Date.now() - velocity.prevTime) / 2; + velocity.y = (image.touchesCurrent.y - velocity.prevPositionY) / (Date.now() - velocity.prevTime) / 2; + if (Math.abs(image.touchesCurrent.x - velocity.prevPositionX) < 2) velocity.x = 0; + if (Math.abs(image.touchesCurrent.y - velocity.prevPositionY) < 2) velocity.y = 0; + velocity.prevPositionX = image.touchesCurrent.x; + velocity.prevPositionY = image.touchesCurrent.y; + velocity.prevTime = Date.now(); + + gesture.$imageWrapEl.transform(`translate3d(${image.currentX}px, ${image.currentY}px,0)`); + }, + onTouchEnd() { + const swiper = this; + const zoom = swiper.zoom; + const { gesture, image, velocity } = zoom; + if (!gesture.$imageEl || gesture.$imageEl.length === 0) return; + if (!image.isTouched || !image.isMoved) { + image.isTouched = false; + image.isMoved = false; + return; + } + image.isTouched = false; + image.isMoved = false; + let momentumDurationX = 300; + let momentumDurationY = 300; + const momentumDistanceX = velocity.x * momentumDurationX; + const newPositionX = image.currentX + momentumDistanceX; + const momentumDistanceY = velocity.y * momentumDurationY; + const newPositionY = image.currentY + momentumDistanceY; + + // Fix duration + if (velocity.x !== 0) momentumDurationX = Math.abs((newPositionX - image.currentX) / velocity.x); + if (velocity.y !== 0) momentumDurationY = Math.abs((newPositionY - image.currentY) / velocity.y); + const momentumDuration = Math.max(momentumDurationX, momentumDurationY); + + image.currentX = newPositionX; + image.currentY = newPositionY; + + // Define if we need image drag + const scaledWidth = image.width * zoom.scale; + const scaledHeight = image.height * zoom.scale; + image.minX = Math.min(((gesture.slideWidth / 2) - (scaledWidth / 2)), 0); + image.maxX = -image.minX; + image.minY = Math.min(((gesture.slideHeight / 2) - (scaledHeight / 2)), 0); + image.maxY = -image.minY; + image.currentX = Math.max(Math.min(image.currentX, image.maxX), image.minX); + image.currentY = Math.max(Math.min(image.currentY, image.maxY), image.minY); + + gesture.$imageWrapEl.transition(momentumDuration).transform(`translate3d(${image.currentX}px, ${image.currentY}px,0)`); + }, + onTransitionEnd() { + const swiper = this; + const zoom = swiper.zoom; + const { gesture } = zoom; + if (gesture.$slideEl && swiper.previousIndex !== swiper.activeIndex) { + gesture.$imageEl.transform('translate3d(0,0,0) scale(1)'); + gesture.$imageWrapEl.transform('translate3d(0,0,0)'); + gesture.$slideEl = undefined; + gesture.$imageEl = undefined; + gesture.$imageWrapEl = undefined; + + zoom.scale = 1; + zoom.currentScale = 1; + } + }, + // Toggle Zoom + toggle(e) { + const swiper = this; + const zoom = swiper.zoom; + + if (zoom.scale && zoom.scale !== 1) { + // Zoom Out + zoom.out(); + } else { + // Zoom In + zoom.in(e); + } + }, + in(e) { + const swiper = this; + + const zoom = swiper.zoom; + const params = swiper.params.zoom; + const { gesture, image } = zoom; + + if (!gesture.$slideEl) { + gesture.$slideEl = swiper.clickedSlide ? $(swiper.clickedSlide) : swiper.slides.eq(swiper.activeIndex); + gesture.$imageEl = gesture.$slideEl.find('img, svg, canvas'); + gesture.$imageWrapEl = gesture.$imageEl.parent(`.${params.containerClass}`); + } + if (!gesture.$imageEl || gesture.$imageEl.length === 0) return; + + gesture.$slideEl.addClass(`${params.zoomedSlideClass}`); + + let touchX; + let touchY; + let offsetX; + let offsetY; + let diffX; + let diffY; + let translateX; + let translateY; + let imageWidth; + let imageHeight; + let scaledWidth; + let scaledHeight; + let translateMinX; + let translateMinY; + let translateMaxX; + let translateMaxY; + let slideWidth; + let slideHeight; + + if (typeof image.touchesStart.x === 'undefined' && e) { + touchX = e.type === 'touchend' ? e.changedTouches[0].pageX : e.pageX; + touchY = e.type === 'touchend' ? e.changedTouches[0].pageY : e.pageY; + } else { + touchX = image.touchesStart.x; + touchY = image.touchesStart.y; + } + + zoom.scale = gesture.$imageWrapEl.attr('data-swiper-zoom') || params.maxRatio; + zoom.currentScale = gesture.$imageWrapEl.attr('data-swiper-zoom') || params.maxRatio; + if (e) { + slideWidth = gesture.$slideEl[0].offsetWidth; + slideHeight = gesture.$slideEl[0].offsetHeight; + offsetX = gesture.$slideEl.offset().left; + offsetY = gesture.$slideEl.offset().top; + diffX = (offsetX + (slideWidth / 2)) - touchX; + diffY = (offsetY + (slideHeight / 2)) - touchY; + + imageWidth = gesture.$imageEl[0].offsetWidth; + imageHeight = gesture.$imageEl[0].offsetHeight; + scaledWidth = imageWidth * zoom.scale; + scaledHeight = imageHeight * zoom.scale; + + translateMinX = Math.min(((slideWidth / 2) - (scaledWidth / 2)), 0); + translateMinY = Math.min(((slideHeight / 2) - (scaledHeight / 2)), 0); + translateMaxX = -translateMinX; + translateMaxY = -translateMinY; + + translateX = diffX * zoom.scale; + translateY = diffY * zoom.scale; + + if (translateX < translateMinX) { + translateX = translateMinX; + } + if (translateX > translateMaxX) { + translateX = translateMaxX; + } + + if (translateY < translateMinY) { + translateY = translateMinY; + } + if (translateY > translateMaxY) { + translateY = translateMaxY; + } + } else { + translateX = 0; + translateY = 0; + } + gesture.$imageWrapEl.transition(300).transform(`translate3d(${translateX}px, ${translateY}px,0)`); + gesture.$imageEl.transition(300).transform(`translate3d(0,0,0) scale(${zoom.scale})`); + }, + out() { + const swiper = this; + + const zoom = swiper.zoom; + const params = swiper.params.zoom; + const { gesture } = zoom; + + if (!gesture.$slideEl) { + gesture.$slideEl = swiper.clickedSlide ? $(swiper.clickedSlide) : swiper.slides.eq(swiper.activeIndex); + gesture.$imageEl = gesture.$slideEl.find('img, svg, canvas'); + gesture.$imageWrapEl = gesture.$imageEl.parent(`.${params.containerClass}`); + } + if (!gesture.$imageEl || gesture.$imageEl.length === 0) return; + + zoom.scale = 1; + zoom.currentScale = 1; + gesture.$imageWrapEl.transition(300).transform('translate3d(0,0,0)'); + gesture.$imageEl.transition(300).transform('translate3d(0,0,0) scale(1)'); + gesture.$slideEl.removeClass(`${params.zoomedSlideClass}`); + gesture.$slideEl = undefined; + }, + // Attach/Detach Events + enable() { + const swiper = this; + const zoom = swiper.zoom; + if (zoom.enabled) return; + zoom.enabled = true; + + const passiveListener = swiper.touchEvents.start === 'touchstart' && Support.passiveListener && swiper.params.passiveListeners ? { passive: true, capture: false } : false; + + // Scale image + if (Support.gestures) { + swiper.$wrapperEl.on('gesturestart', '.swiper-slide', zoom.onGestureStart, passiveListener); + swiper.$wrapperEl.on('gesturechange', '.swiper-slide', zoom.onGestureChange, passiveListener); + swiper.$wrapperEl.on('gestureend', '.swiper-slide', zoom.onGestureEnd, passiveListener); + } else if (swiper.touchEvents.start === 'touchstart') { + swiper.$wrapperEl.on(swiper.touchEvents.start, '.swiper-slide', zoom.onGestureStart, passiveListener); + swiper.$wrapperEl.on(swiper.touchEvents.move, '.swiper-slide', zoom.onGestureChange, passiveListener); + swiper.$wrapperEl.on(swiper.touchEvents.end, '.swiper-slide', zoom.onGestureEnd, passiveListener); + } + + // Move image + swiper.$wrapperEl.on(swiper.touchEvents.move, `.${swiper.params.zoom.containerClass}`, zoom.onTouchMove); + }, + disable() { + const swiper = this; + const zoom = swiper.zoom; + if (!zoom.enabled) return; + + swiper.zoom.enabled = false; + + const passiveListener = swiper.touchEvents.start === 'touchstart' && Support.passiveListener && swiper.params.passiveListeners ? { passive: true, capture: false } : false; + + // Scale image + if (Support.gestures) { + swiper.$wrapperEl.off('gesturestart', '.swiper-slide', zoom.onGestureStart, passiveListener); + swiper.$wrapperEl.off('gesturechange', '.swiper-slide', zoom.onGestureChange, passiveListener); + swiper.$wrapperEl.off('gestureend', '.swiper-slide', zoom.onGestureEnd, passiveListener); + } else if (swiper.touchEvents.start === 'touchstart') { + swiper.$wrapperEl.off(swiper.touchEvents.start, '.swiper-slide', zoom.onGestureStart, passiveListener); + swiper.$wrapperEl.off(swiper.touchEvents.move, '.swiper-slide', zoom.onGestureChange, passiveListener); + swiper.$wrapperEl.off(swiper.touchEvents.end, '.swiper-slide', zoom.onGestureEnd, passiveListener); + } + + // Move image + swiper.$wrapperEl.off(swiper.touchEvents.move, `.${swiper.params.zoom.containerClass}`, zoom.onTouchMove); + }, +}; + +var zoom = { + name: 'zoom', + params: { + zoom: { + enabled: false, + maxRatio: 3, + minRatio: 1, + toggle: true, + containerClass: 'swiper-zoom-container', + zoomedSlideClass: 'swiper-slide-zoomed', + }, + }, + create() { + const swiper = this; + const zoom = { + enabled: false, + scale: 1, + currentScale: 1, + isScaling: false, + gesture: { + $slideEl: undefined, + slideWidth: undefined, + slideHeight: undefined, + $imageEl: undefined, + $imageWrapEl: undefined, + maxRatio: 3, + }, + image: { + isTouched: undefined, + isMoved: undefined, + currentX: undefined, + currentY: undefined, + minX: undefined, + minY: undefined, + maxX: undefined, + maxY: undefined, + width: undefined, + height: undefined, + startX: undefined, + startY: undefined, + touchesStart: {}, + touchesCurrent: {}, + }, + velocity: { + x: undefined, + y: undefined, + prevPositionX: undefined, + prevPositionY: undefined, + prevTime: undefined, + }, + }; + ('onGestureStart onGestureChange onGestureEnd onTouchStart onTouchMove onTouchEnd onTransitionEnd toggle enable disable in out').split(' ').forEach((methodName) => { + zoom[methodName] = Zoom[methodName].bind(swiper); + }); + Utils.extend(swiper, { + zoom, + }); + }, + on: { + init() { + const swiper = this; + if (swiper.params.zoom.enabled) { + swiper.zoom.enable(); + } + }, + destroy() { + const swiper = this; + swiper.zoom.disable(); + }, + touchStart(e) { + const swiper = this; + if (!swiper.zoom.enabled) return; + swiper.zoom.onTouchStart(e); + }, + touchEnd(e) { + const swiper = this; + if (!swiper.zoom.enabled) return; + swiper.zoom.onTouchEnd(e); + }, + doubleTap(e) { + const swiper = this; + if (swiper.params.zoom.enabled && swiper.zoom.enabled && swiper.params.zoom.toggle) { + swiper.zoom.toggle(e); + } + }, + transitionEnd() { + const swiper = this; + if (swiper.zoom.enabled && swiper.params.zoom.enabled) { + swiper.zoom.onTransitionEnd(); + } + }, + }, +}; + /* eslint no-underscore-dangle: "off" */ const Autoplay = { @@ -5108,6 +5614,6 @@ if (typeof Swiper.use === 'undefined') { Swiper.use(components); -Swiper.use([pagination, scrollbar, autoplay]); +Swiper.use([pagination, scrollbar, autoplay, zoom]); export { Swiper };