photoswipe
Version:
JavaScript gallery
249 lines (208 loc) • 7.58 kB
JavaScript
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;