UNPKG

photoswipe

Version:
249 lines (208 loc) 7.58 kB
import { equalizePoints, getDistanceBetween, clamp, pointsEqual } from '../util/util.js'; /** @typedef {import('../photoswipe.js').Point} Point */ /** @typedef {import('./gestures.js').default} Gestures */ const UPPER_ZOOM_FRICTION = 0.05; const LOWER_ZOOM_FRICTION = 0.15; /** * Get center point between two points * * @param {Point} p * @param {Point} p1 * @param {Point} p2 */ function getZoomPointsCenter(p, p1, p2) { p.x = (p1.x + p2.x) / 2; p.y = (p1.y + p2.y) / 2; return p; } class ZoomHandler { /** * @param {Gestures} gestures */ constructor(gestures) { this.gestures = gestures; this.pswp = this.gestures.pswp; /** @type {Point} */ this._startPan = {}; /** @type {Point} */ this._startZoomPoint = {}; /** @type {Point} */ this._zoomPoint = {}; } start() { this._startZoomLevel = this.pswp.currSlide.currZoomLevel; equalizePoints(this._startPan, this.pswp.currSlide.pan); this.pswp.animations.stopAllPan(); this._wasOverFitZoomLevel = false; } change() { const { p1, startP1, p2, startP2, pswp } = this.gestures; const { currSlide } = pswp; const minZoomLevel = currSlide.zoomLevels.min; const maxZoomLevel = currSlide.zoomLevels.max; if (!currSlide.isZoomable() || pswp.mainScroll.isShifted()) { return; } getZoomPointsCenter(this._startZoomPoint, startP1, startP2); getZoomPointsCenter(this._zoomPoint, p1, p2); let currZoomLevel = (1 / getDistanceBetween(startP1, startP2)) * getDistanceBetween(p1, p2) * this._startZoomLevel; // slightly over the zoom.fit if (currZoomLevel > currSlide.zoomLevels.initial + (currSlide.zoomLevels.initial / 15)) { this._wasOverFitZoomLevel = true; } if (currZoomLevel < minZoomLevel) { if (pswp.options.pinchToClose && !this._wasOverFitZoomLevel && this._startZoomLevel <= currSlide.zoomLevels.initial) { // fade out background if zooming out const bgOpacity = 1 - ((minZoomLevel - currZoomLevel) / (minZoomLevel / 1.2)); if (!pswp.dispatch('pinchClose', { bgOpacity }).defaultPrevented) { pswp.applyBgOpacity(bgOpacity); } } else { // Apply the friction if zoom level is below the min currZoomLevel = minZoomLevel - (minZoomLevel - currZoomLevel) * LOWER_ZOOM_FRICTION; } } else if (currZoomLevel > maxZoomLevel) { // Apply the friction if zoom level is above the max currZoomLevel = maxZoomLevel + (currZoomLevel - maxZoomLevel) * UPPER_ZOOM_FRICTION; } currSlide.pan.x = this._calculatePanForZoomLevel('x', currZoomLevel); currSlide.pan.y = this._calculatePanForZoomLevel('y', currZoomLevel); currSlide.setZoomLevel(currZoomLevel); currSlide.applyCurrentZoomPan(); } end() { const { pswp } = this; const { currSlide } = pswp; if (currSlide.currZoomLevel < currSlide.zoomLevels.initial && !this._wasOverFitZoomLevel && pswp.options.pinchToClose) { pswp.close(); } else { this.correctZoomPan(); } } /** * @private * @param {'x' | 'y'} axis * @param {number} currZoomLevel */ _calculatePanForZoomLevel(axis, currZoomLevel) { const zoomFactor = currZoomLevel / this._startZoomLevel; return this._zoomPoint[axis] - ((this._startZoomPoint[axis] - this._startPan[axis]) * zoomFactor); } /** * Correct currZoomLevel and pan if they are * beyond minimum or maximum values. * With animation. * * @param {boolean=} ignoreGesture * Wether gesture coordinates should be ignored when calculating destination pan position. */ correctZoomPan(ignoreGesture) { const { pswp } = this; const { currSlide } = pswp; if (!currSlide.isZoomable()) { return; } if (this._zoomPoint.x === undefined) { ignoreGesture = true; } const prevZoomLevel = currSlide.currZoomLevel; /** @type {number} */ let destinationZoomLevel; let currZoomLevelNeedsChange = true; if (prevZoomLevel < currSlide.zoomLevels.initial) { destinationZoomLevel = currSlide.zoomLevels.initial; // zoom to min } else if (prevZoomLevel > currSlide.zoomLevels.max) { destinationZoomLevel = currSlide.zoomLevels.max; // zoom to max } else { currZoomLevelNeedsChange = false; destinationZoomLevel = prevZoomLevel; } const initialBgOpacity = pswp.bgOpacity; const restoreBgOpacity = pswp.bgOpacity < 1; const initialPan = equalizePoints({}, currSlide.pan); let destinationPan = equalizePoints({}, initialPan); if (ignoreGesture) { this._zoomPoint.x = 0; this._zoomPoint.y = 0; this._startZoomPoint.x = 0; this._startZoomPoint.y = 0; this._startZoomLevel = prevZoomLevel; equalizePoints(this._startPan, initialPan); } if (currZoomLevelNeedsChange) { destinationPan = { x: this._calculatePanForZoomLevel('x', destinationZoomLevel), y: this._calculatePanForZoomLevel('y', destinationZoomLevel) }; } // set zoom level, so pan bounds are updated according to it currSlide.setZoomLevel(destinationZoomLevel); destinationPan = { x: currSlide.bounds.correctPan('x', destinationPan.x), y: currSlide.bounds.correctPan('y', destinationPan.y) }; // return zoom level and its bounds to initial currSlide.setZoomLevel(prevZoomLevel); let panNeedsChange = true; if (pointsEqual(destinationPan, initialPan)) { panNeedsChange = false; } if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) { // update resolution after gesture currSlide._setResolution(destinationZoomLevel); currSlide.applyCurrentZoomPan(); // nothing to animate return; } pswp.animations.stopAllPan(); pswp.animations.startSpring({ isPan: true, start: 0, end: 1000, velocity: 0, dampingRatio: 1, naturalFrequency: 40, onUpdate: (now) => { now /= 1000; // 0 - start, 1 - end if (panNeedsChange || currZoomLevelNeedsChange) { if (panNeedsChange) { currSlide.pan.x = initialPan.x + (destinationPan.x - initialPan.x) * now; currSlide.pan.y = initialPan.y + (destinationPan.y - initialPan.y) * now; } if (currZoomLevelNeedsChange) { const newZoomLevel = prevZoomLevel + (destinationZoomLevel - prevZoomLevel) * now; currSlide.setZoomLevel(newZoomLevel); } currSlide.applyCurrentZoomPan(); } // Restore background opacity if (restoreBgOpacity && pswp.bgOpacity < 1) { // We clamp opacity to keep it between 0 and 1. // As progress ratio can be larger than 1 due to overshoot, // and we do not want to bounce opacity. pswp.applyBgOpacity(clamp( initialBgOpacity + (1 - initialBgOpacity) * now, 0, 1 )); } }, onComplete: () => { // update resolution after transition ends currSlide._setResolution(destinationZoomLevel); currSlide.applyCurrentZoomPan(); } }); } } export default ZoomHandler;