Support disabling panning at min scale and selectively passing down pointer events
At min scale we can now disable panning (but still get pinch actions). In that case the pan will be passed down. Once the pan is turned into a pinch, it will no longer be passed down, and a pointercancel will be triggered on the parent.
This commit is contained in:
parent
390b0e0278
commit
a94fb753a1
8 changed files with 667 additions and 17 deletions
2
dist/pinch-zoom-min.js
vendored
2
dist/pinch-zoom-min.js
vendored
File diff suppressed because one or more lines are too long
127
dist/pinch-zoom.cjs.js
vendored
127
dist/pinch-zoom.cjs.js
vendored
|
@ -35,6 +35,11 @@ var css = "pinch-zoom {\n display: block;\n overflow: hidden;\n touch-action:
|
|||
styleInject(css);
|
||||
|
||||
const minScaleAttr = 'min-scale';
|
||||
const allowPanMinScaleAttr = 'allow-pan-min-scale';
|
||||
const resetToMinScaleLimitAttr = 'reset-to-min-scale-limit';
|
||||
const reachMinScaleStrategyAttr = 'reach-min-scale-strategy';
|
||||
const stopPropagateHandledAttr = 'stop-propagate-handled';
|
||||
const reachMinScaleStrategyDefault = 'none';
|
||||
function getDistance(a, b) {
|
||||
if (!b)
|
||||
return 0;
|
||||
|
@ -69,6 +74,11 @@ function createPoint() {
|
|||
return getSVG().createSVGPoint();
|
||||
}
|
||||
const MIN_SCALE = 0.01;
|
||||
const ALLOW_PAN_MIN_SCALE = -1;
|
||||
const RESET_TO_MIN_SCALE_LIMIT = -1;
|
||||
const roundedCmp = (a, b) => {
|
||||
return Math.round(a * 100) - Math.round(b * 100);
|
||||
};
|
||||
class PinchZoom extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -85,11 +95,20 @@ class PinchZoom extends HTMLElement {
|
|||
// We only want to track 2 pointers at most
|
||||
if (pointerTracker.currentPointers.length === 2 || !this._positioningEl)
|
||||
return false;
|
||||
const isPan = pointerTracker.currentPointers.length + 1 === 1;
|
||||
const handled = !(isPan && !this._allowPan());
|
||||
if (handled) {
|
||||
this._maybeStopPropagate(event);
|
||||
this._maybeEmitCancel([pointer, ...pointerTracker.currentPointers]);
|
||||
}
|
||||
event.preventDefault();
|
||||
return true;
|
||||
},
|
||||
move: (previousPointers) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers);
|
||||
move: (previousPointers, _, event) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers, event);
|
||||
},
|
||||
end: (pointer, event) => {
|
||||
this._onPointerEnd(pointer, pointerTracker.currentPointers, event);
|
||||
},
|
||||
});
|
||||
this.addEventListener('wheel', event => this._onWheel(event));
|
||||
|
@ -114,6 +133,49 @@ class PinchZoom extends HTMLElement {
|
|||
set minScale(value) {
|
||||
this.setAttribute(minScaleAttr, String(value));
|
||||
}
|
||||
get reachMinScaleStrategy() {
|
||||
const attrValue = this.getAttribute(reachMinScaleStrategyAttr);
|
||||
const v = attrValue;
|
||||
return v || reachMinScaleStrategyDefault;
|
||||
}
|
||||
set reachMinScaleStrategy(value) {
|
||||
this.setAttribute(reachMinScaleStrategyAttr, value);
|
||||
}
|
||||
get allowPanMinScale() {
|
||||
const attrValue = this.getAttribute(allowPanMinScaleAttr);
|
||||
if (!attrValue)
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value))
|
||||
return Math.max(ALLOW_PAN_MIN_SCALE, value);
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
}
|
||||
set allowPanMinScale(value) {
|
||||
this.setAttribute(allowPanMinScaleAttr, String(value));
|
||||
}
|
||||
get resetToMinScaleLimit() {
|
||||
const attrValue = this.getAttribute(resetToMinScaleLimitAttr);
|
||||
if (!attrValue)
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value))
|
||||
return Math.max(RESET_TO_MIN_SCALE_LIMIT, value);
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
}
|
||||
set resetToMinScaleLimit(value) {
|
||||
this.setAttribute(resetToMinScaleLimitAttr, String(value));
|
||||
}
|
||||
get stopPropagateHandled() {
|
||||
return this.hasAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
set stopPropagateHandled(value) {
|
||||
if (value) {
|
||||
this.setAttribute(stopPropagateHandledAttr, '');
|
||||
}
|
||||
else {
|
||||
this.removeAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
this._stageElChange();
|
||||
}
|
||||
|
@ -264,16 +326,24 @@ class PinchZoom extends HTMLElement {
|
|||
// ctrlKey is true when pinch-zooming on a trackpad.
|
||||
const divisor = ctrlKey ? 100 : 300;
|
||||
const scaleDiff = 1 - deltaY / divisor;
|
||||
const isZoomOut = scaleDiff < 1;
|
||||
this._applyChange({
|
||||
scaleDiff,
|
||||
originX: event.clientX - currentRect.left,
|
||||
originY: event.clientY - currentRect.top,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
if (isZoomOut) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
}
|
||||
_onPointerMove(previousPointers, currentPointers) {
|
||||
_onPointerMove(previousPointers, currentPointers, event) {
|
||||
if (!this._positioningEl)
|
||||
return;
|
||||
const isPan = previousPointers.length < 2;
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
// Combine next points with previous points
|
||||
const currentRect = this._positioningEl.getBoundingClientRect();
|
||||
// For calculating panning movement
|
||||
|
@ -292,6 +362,34 @@ class PinchZoom extends HTMLElement {
|
|||
panY: newMidpoint.clientY - prevMidpoint.clientY,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
_maybeResetScale() {
|
||||
if (roundedCmp(this.scale, this.resetToMinScaleLimit) <= 0) {
|
||||
this._resetToMinScale();
|
||||
}
|
||||
}
|
||||
_onPointerEnd(pointer, currentPointers, event) {
|
||||
if (!this._positioningEl)
|
||||
return;
|
||||
const totalPointers = 1 + currentPointers.length;
|
||||
const isPinch = totalPointers >= 2;
|
||||
const isPan = totalPointers == 1;
|
||||
if (isPinch) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
_resetToMinScale() {
|
||||
if (this.reachMinScaleStrategy === 'reset') {
|
||||
this.setTransform({ scale: this.minScale, x: 0, y: 0 });
|
||||
}
|
||||
else {
|
||||
this.setTransform({ scale: this.minScale });
|
||||
}
|
||||
}
|
||||
/** Transform the view & fire a change event */
|
||||
_applyChange(opts = {}) {
|
||||
|
@ -315,6 +413,29 @@ class PinchZoom extends HTMLElement {
|
|||
y: matrix.f,
|
||||
});
|
||||
}
|
||||
_maybeStopPropagate(event) {
|
||||
if (this.stopPropagateHandled) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
_allowPan() {
|
||||
return (this.allowPanMinScale > 0
|
||||
&& roundedCmp(this.scale, this.allowPanMinScale) > 0);
|
||||
}
|
||||
_maybeEmitCancel(pointers) {
|
||||
const makeCancelEvent = (pointer) => (new PointerEvent('pointercancel', {
|
||||
pointerId: pointer.id,
|
||||
clientX: pointer.clientX,
|
||||
clientY: pointer.clientY,
|
||||
}));
|
||||
if (this.stopPropagateHandled) {
|
||||
pointers.forEach(p => {
|
||||
if (this.parentElement && typeof this.parentElement.dispatchEvent === 'function') {
|
||||
this.parentElement.dispatchEvent(makeCancelEvent(p));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('pinch-zoom', PinchZoom);
|
||||
|
|
11
dist/pinch-zoom.d.ts
vendored
11
dist/pinch-zoom.d.ts
vendored
|
@ -11,6 +11,7 @@ interface SetTransformOpts extends ChangeOptions {
|
|||
y?: number;
|
||||
}
|
||||
declare type ScaleRelativeToValues = 'container' | 'content';
|
||||
declare type ReachMinScaleStrategy = 'reset' | 'none';
|
||||
export interface ScaleToOpts extends ChangeOptions {
|
||||
/** Transform origin. Can be a number, or string percent, eg "50%" */
|
||||
originX?: number | string;
|
||||
|
@ -26,6 +27,10 @@ export default class PinchZoom extends HTMLElement {
|
|||
constructor();
|
||||
attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
|
||||
minScale: number;
|
||||
reachMinScaleStrategy: ReachMinScaleStrategy;
|
||||
allowPanMinScale: number;
|
||||
resetToMinScaleLimit: number;
|
||||
stopPropagateHandled: boolean;
|
||||
connectedCallback(): void;
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
|
@ -51,7 +56,13 @@ export default class PinchZoom extends HTMLElement {
|
|||
private _stageElChange;
|
||||
private _onWheel;
|
||||
private _onPointerMove;
|
||||
private _maybeResetScale;
|
||||
private _onPointerEnd;
|
||||
private _resetToMinScale;
|
||||
/** Transform the view & fire a change event */
|
||||
private _applyChange;
|
||||
private _maybeStopPropagate;
|
||||
private _allowPan;
|
||||
private _maybeEmitCancel;
|
||||
}
|
||||
export {};
|
||||
|
|
127
dist/pinch-zoom.es.js
vendored
127
dist/pinch-zoom.es.js
vendored
|
@ -31,6 +31,11 @@ var css = "pinch-zoom {\n display: block;\n overflow: hidden;\n touch-action:
|
|||
styleInject(css);
|
||||
|
||||
const minScaleAttr = 'min-scale';
|
||||
const allowPanMinScaleAttr = 'allow-pan-min-scale';
|
||||
const resetToMinScaleLimitAttr = 'reset-to-min-scale-limit';
|
||||
const reachMinScaleStrategyAttr = 'reach-min-scale-strategy';
|
||||
const stopPropagateHandledAttr = 'stop-propagate-handled';
|
||||
const reachMinScaleStrategyDefault = 'none';
|
||||
function getDistance(a, b) {
|
||||
if (!b)
|
||||
return 0;
|
||||
|
@ -65,6 +70,11 @@ function createPoint() {
|
|||
return getSVG().createSVGPoint();
|
||||
}
|
||||
const MIN_SCALE = 0.01;
|
||||
const ALLOW_PAN_MIN_SCALE = -1;
|
||||
const RESET_TO_MIN_SCALE_LIMIT = -1;
|
||||
const roundedCmp = (a, b) => {
|
||||
return Math.round(a * 100) - Math.round(b * 100);
|
||||
};
|
||||
class PinchZoom extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -81,11 +91,20 @@ class PinchZoom extends HTMLElement {
|
|||
// We only want to track 2 pointers at most
|
||||
if (pointerTracker.currentPointers.length === 2 || !this._positioningEl)
|
||||
return false;
|
||||
const isPan = pointerTracker.currentPointers.length + 1 === 1;
|
||||
const handled = !(isPan && !this._allowPan());
|
||||
if (handled) {
|
||||
this._maybeStopPropagate(event);
|
||||
this._maybeEmitCancel([pointer, ...pointerTracker.currentPointers]);
|
||||
}
|
||||
event.preventDefault();
|
||||
return true;
|
||||
},
|
||||
move: (previousPointers) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers);
|
||||
move: (previousPointers, _, event) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers, event);
|
||||
},
|
||||
end: (pointer, event) => {
|
||||
this._onPointerEnd(pointer, pointerTracker.currentPointers, event);
|
||||
},
|
||||
});
|
||||
this.addEventListener('wheel', event => this._onWheel(event));
|
||||
|
@ -110,6 +129,49 @@ class PinchZoom extends HTMLElement {
|
|||
set minScale(value) {
|
||||
this.setAttribute(minScaleAttr, String(value));
|
||||
}
|
||||
get reachMinScaleStrategy() {
|
||||
const attrValue = this.getAttribute(reachMinScaleStrategyAttr);
|
||||
const v = attrValue;
|
||||
return v || reachMinScaleStrategyDefault;
|
||||
}
|
||||
set reachMinScaleStrategy(value) {
|
||||
this.setAttribute(reachMinScaleStrategyAttr, value);
|
||||
}
|
||||
get allowPanMinScale() {
|
||||
const attrValue = this.getAttribute(allowPanMinScaleAttr);
|
||||
if (!attrValue)
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value))
|
||||
return Math.max(ALLOW_PAN_MIN_SCALE, value);
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
}
|
||||
set allowPanMinScale(value) {
|
||||
this.setAttribute(allowPanMinScaleAttr, String(value));
|
||||
}
|
||||
get resetToMinScaleLimit() {
|
||||
const attrValue = this.getAttribute(resetToMinScaleLimitAttr);
|
||||
if (!attrValue)
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value))
|
||||
return Math.max(RESET_TO_MIN_SCALE_LIMIT, value);
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
}
|
||||
set resetToMinScaleLimit(value) {
|
||||
this.setAttribute(resetToMinScaleLimitAttr, String(value));
|
||||
}
|
||||
get stopPropagateHandled() {
|
||||
return this.hasAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
set stopPropagateHandled(value) {
|
||||
if (value) {
|
||||
this.setAttribute(stopPropagateHandledAttr, '');
|
||||
}
|
||||
else {
|
||||
this.removeAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
this._stageElChange();
|
||||
}
|
||||
|
@ -260,16 +322,24 @@ class PinchZoom extends HTMLElement {
|
|||
// ctrlKey is true when pinch-zooming on a trackpad.
|
||||
const divisor = ctrlKey ? 100 : 300;
|
||||
const scaleDiff = 1 - deltaY / divisor;
|
||||
const isZoomOut = scaleDiff < 1;
|
||||
this._applyChange({
|
||||
scaleDiff,
|
||||
originX: event.clientX - currentRect.left,
|
||||
originY: event.clientY - currentRect.top,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
if (isZoomOut) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
}
|
||||
_onPointerMove(previousPointers, currentPointers) {
|
||||
_onPointerMove(previousPointers, currentPointers, event) {
|
||||
if (!this._positioningEl)
|
||||
return;
|
||||
const isPan = previousPointers.length < 2;
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
// Combine next points with previous points
|
||||
const currentRect = this._positioningEl.getBoundingClientRect();
|
||||
// For calculating panning movement
|
||||
|
@ -288,6 +358,34 @@ class PinchZoom extends HTMLElement {
|
|||
panY: newMidpoint.clientY - prevMidpoint.clientY,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
_maybeResetScale() {
|
||||
if (roundedCmp(this.scale, this.resetToMinScaleLimit) <= 0) {
|
||||
this._resetToMinScale();
|
||||
}
|
||||
}
|
||||
_onPointerEnd(pointer, currentPointers, event) {
|
||||
if (!this._positioningEl)
|
||||
return;
|
||||
const totalPointers = 1 + currentPointers.length;
|
||||
const isPinch = totalPointers >= 2;
|
||||
const isPan = totalPointers == 1;
|
||||
if (isPinch) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
_resetToMinScale() {
|
||||
if (this.reachMinScaleStrategy === 'reset') {
|
||||
this.setTransform({ scale: this.minScale, x: 0, y: 0 });
|
||||
}
|
||||
else {
|
||||
this.setTransform({ scale: this.minScale });
|
||||
}
|
||||
}
|
||||
/** Transform the view & fire a change event */
|
||||
_applyChange(opts = {}) {
|
||||
|
@ -311,6 +409,29 @@ class PinchZoom extends HTMLElement {
|
|||
y: matrix.f,
|
||||
});
|
||||
}
|
||||
_maybeStopPropagate(event) {
|
||||
if (this.stopPropagateHandled) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
_allowPan() {
|
||||
return (this.allowPanMinScale > 0
|
||||
&& roundedCmp(this.scale, this.allowPanMinScale) > 0);
|
||||
}
|
||||
_maybeEmitCancel(pointers) {
|
||||
const makeCancelEvent = (pointer) => (new PointerEvent('pointercancel', {
|
||||
pointerId: pointer.id,
|
||||
clientX: pointer.clientX,
|
||||
clientY: pointer.clientY,
|
||||
}));
|
||||
if (this.stopPropagateHandled) {
|
||||
pointers.forEach(p => {
|
||||
if (this.parentElement && typeof this.parentElement.dispatchEvent === 'function') {
|
||||
this.parentElement.dispatchEvent(makeCancelEvent(p));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('pinch-zoom', PinchZoom);
|
||||
|
|
127
dist/pinch-zoom.js
vendored
127
dist/pinch-zoom.js
vendored
|
@ -216,6 +216,11 @@ var PinchZoom = (function () {
|
|||
styleInject(css);
|
||||
|
||||
const minScaleAttr = 'min-scale';
|
||||
const allowPanMinScaleAttr = 'allow-pan-min-scale';
|
||||
const resetToMinScaleLimitAttr = 'reset-to-min-scale-limit';
|
||||
const reachMinScaleStrategyAttr = 'reach-min-scale-strategy';
|
||||
const stopPropagateHandledAttr = 'stop-propagate-handled';
|
||||
const reachMinScaleStrategyDefault = 'none';
|
||||
function getDistance(a, b) {
|
||||
if (!b)
|
||||
return 0;
|
||||
|
@ -250,6 +255,11 @@ var PinchZoom = (function () {
|
|||
return getSVG().createSVGPoint();
|
||||
}
|
||||
const MIN_SCALE = 0.01;
|
||||
const ALLOW_PAN_MIN_SCALE = -1;
|
||||
const RESET_TO_MIN_SCALE_LIMIT = -1;
|
||||
const roundedCmp = (a, b) => {
|
||||
return Math.round(a * 100) - Math.round(b * 100);
|
||||
};
|
||||
class PinchZoom extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -266,11 +276,20 @@ var PinchZoom = (function () {
|
|||
// We only want to track 2 pointers at most
|
||||
if (pointerTracker.currentPointers.length === 2 || !this._positioningEl)
|
||||
return false;
|
||||
const isPan = pointerTracker.currentPointers.length + 1 === 1;
|
||||
const handled = !(isPan && !this._allowPan());
|
||||
if (handled) {
|
||||
this._maybeStopPropagate(event);
|
||||
this._maybeEmitCancel([pointer, ...pointerTracker.currentPointers]);
|
||||
}
|
||||
event.preventDefault();
|
||||
return true;
|
||||
},
|
||||
move: (previousPointers) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers);
|
||||
move: (previousPointers, _, event) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers, event);
|
||||
},
|
||||
end: (pointer, event) => {
|
||||
this._onPointerEnd(pointer, pointerTracker.currentPointers, event);
|
||||
},
|
||||
});
|
||||
this.addEventListener('wheel', event => this._onWheel(event));
|
||||
|
@ -295,6 +314,49 @@ var PinchZoom = (function () {
|
|||
set minScale(value) {
|
||||
this.setAttribute(minScaleAttr, String(value));
|
||||
}
|
||||
get reachMinScaleStrategy() {
|
||||
const attrValue = this.getAttribute(reachMinScaleStrategyAttr);
|
||||
const v = attrValue;
|
||||
return v || reachMinScaleStrategyDefault;
|
||||
}
|
||||
set reachMinScaleStrategy(value) {
|
||||
this.setAttribute(reachMinScaleStrategyAttr, value);
|
||||
}
|
||||
get allowPanMinScale() {
|
||||
const attrValue = this.getAttribute(allowPanMinScaleAttr);
|
||||
if (!attrValue)
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value))
|
||||
return Math.max(ALLOW_PAN_MIN_SCALE, value);
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
}
|
||||
set allowPanMinScale(value) {
|
||||
this.setAttribute(allowPanMinScaleAttr, String(value));
|
||||
}
|
||||
get resetToMinScaleLimit() {
|
||||
const attrValue = this.getAttribute(resetToMinScaleLimitAttr);
|
||||
if (!attrValue)
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value))
|
||||
return Math.max(RESET_TO_MIN_SCALE_LIMIT, value);
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
}
|
||||
set resetToMinScaleLimit(value) {
|
||||
this.setAttribute(resetToMinScaleLimitAttr, String(value));
|
||||
}
|
||||
get stopPropagateHandled() {
|
||||
return this.hasAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
set stopPropagateHandled(value) {
|
||||
if (value) {
|
||||
this.setAttribute(stopPropagateHandledAttr, '');
|
||||
}
|
||||
else {
|
||||
this.removeAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
this._stageElChange();
|
||||
}
|
||||
|
@ -445,16 +507,24 @@ var PinchZoom = (function () {
|
|||
// ctrlKey is true when pinch-zooming on a trackpad.
|
||||
const divisor = ctrlKey ? 100 : 300;
|
||||
const scaleDiff = 1 - deltaY / divisor;
|
||||
const isZoomOut = scaleDiff < 1;
|
||||
this._applyChange({
|
||||
scaleDiff,
|
||||
originX: event.clientX - currentRect.left,
|
||||
originY: event.clientY - currentRect.top,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
if (isZoomOut) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
}
|
||||
_onPointerMove(previousPointers, currentPointers) {
|
||||
_onPointerMove(previousPointers, currentPointers, event) {
|
||||
if (!this._positioningEl)
|
||||
return;
|
||||
const isPan = previousPointers.length < 2;
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
// Combine next points with previous points
|
||||
const currentRect = this._positioningEl.getBoundingClientRect();
|
||||
// For calculating panning movement
|
||||
|
@ -473,6 +543,34 @@ var PinchZoom = (function () {
|
|||
panY: newMidpoint.clientY - prevMidpoint.clientY,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
_maybeResetScale() {
|
||||
if (roundedCmp(this.scale, this.resetToMinScaleLimit) <= 0) {
|
||||
this._resetToMinScale();
|
||||
}
|
||||
}
|
||||
_onPointerEnd(pointer, currentPointers, event) {
|
||||
if (!this._positioningEl)
|
||||
return;
|
||||
const totalPointers = 1 + currentPointers.length;
|
||||
const isPinch = totalPointers >= 2;
|
||||
const isPan = totalPointers == 1;
|
||||
if (isPinch) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
_resetToMinScale() {
|
||||
if (this.reachMinScaleStrategy === 'reset') {
|
||||
this.setTransform({ scale: this.minScale, x: 0, y: 0 });
|
||||
}
|
||||
else {
|
||||
this.setTransform({ scale: this.minScale });
|
||||
}
|
||||
}
|
||||
/** Transform the view & fire a change event */
|
||||
_applyChange(opts = {}) {
|
||||
|
@ -496,6 +594,29 @@ var PinchZoom = (function () {
|
|||
y: matrix.f,
|
||||
});
|
||||
}
|
||||
_maybeStopPropagate(event) {
|
||||
if (this.stopPropagateHandled) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
_allowPan() {
|
||||
return (this.allowPanMinScale > 0
|
||||
&& roundedCmp(this.scale, this.allowPanMinScale) > 0);
|
||||
}
|
||||
_maybeEmitCancel(pointers) {
|
||||
const makeCancelEvent = (pointer) => (new PointerEvent('pointercancel', {
|
||||
pointerId: pointer.id,
|
||||
clientX: pointer.clientX,
|
||||
clientY: pointer.clientY,
|
||||
}));
|
||||
if (this.stopPropagateHandled) {
|
||||
pointers.forEach(p => {
|
||||
if (this.parentElement && typeof this.parentElement.dispatchEvent === 'function') {
|
||||
this.parentElement.dispatchEvent(makeCancelEvent(p));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('pinch-zoom', PinchZoom);
|
||||
|
|
127
dist/pinch-zoom.mjs
vendored
127
dist/pinch-zoom.mjs
vendored
|
@ -31,6 +31,11 @@ var css = "pinch-zoom {\n display: block;\n overflow: hidden;\n touch-action:
|
|||
styleInject(css);
|
||||
|
||||
const minScaleAttr = 'min-scale';
|
||||
const allowPanMinScaleAttr = 'allow-pan-min-scale';
|
||||
const resetToMinScaleLimitAttr = 'reset-to-min-scale-limit';
|
||||
const reachMinScaleStrategyAttr = 'reach-min-scale-strategy';
|
||||
const stopPropagateHandledAttr = 'stop-propagate-handled';
|
||||
const reachMinScaleStrategyDefault = 'none';
|
||||
function getDistance(a, b) {
|
||||
if (!b)
|
||||
return 0;
|
||||
|
@ -65,6 +70,11 @@ function createPoint() {
|
|||
return getSVG().createSVGPoint();
|
||||
}
|
||||
const MIN_SCALE = 0.01;
|
||||
const ALLOW_PAN_MIN_SCALE = -1;
|
||||
const RESET_TO_MIN_SCALE_LIMIT = -1;
|
||||
const roundedCmp = (a, b) => {
|
||||
return Math.round(a * 100) - Math.round(b * 100);
|
||||
};
|
||||
class PinchZoom extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -81,11 +91,20 @@ class PinchZoom extends HTMLElement {
|
|||
// We only want to track 2 pointers at most
|
||||
if (pointerTracker.currentPointers.length === 2 || !this._positioningEl)
|
||||
return false;
|
||||
const isPan = pointerTracker.currentPointers.length + 1 === 1;
|
||||
const handled = !(isPan && !this._allowPan());
|
||||
if (handled) {
|
||||
this._maybeStopPropagate(event);
|
||||
this._maybeEmitCancel([pointer, ...pointerTracker.currentPointers]);
|
||||
}
|
||||
event.preventDefault();
|
||||
return true;
|
||||
},
|
||||
move: (previousPointers) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers);
|
||||
move: (previousPointers, _, event) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers, event);
|
||||
},
|
||||
end: (pointer, event) => {
|
||||
this._onPointerEnd(pointer, pointerTracker.currentPointers, event);
|
||||
},
|
||||
});
|
||||
this.addEventListener('wheel', event => this._onWheel(event));
|
||||
|
@ -110,6 +129,49 @@ class PinchZoom extends HTMLElement {
|
|||
set minScale(value) {
|
||||
this.setAttribute(minScaleAttr, String(value));
|
||||
}
|
||||
get reachMinScaleStrategy() {
|
||||
const attrValue = this.getAttribute(reachMinScaleStrategyAttr);
|
||||
const v = attrValue;
|
||||
return v || reachMinScaleStrategyDefault;
|
||||
}
|
||||
set reachMinScaleStrategy(value) {
|
||||
this.setAttribute(reachMinScaleStrategyAttr, value);
|
||||
}
|
||||
get allowPanMinScale() {
|
||||
const attrValue = this.getAttribute(allowPanMinScaleAttr);
|
||||
if (!attrValue)
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value))
|
||||
return Math.max(ALLOW_PAN_MIN_SCALE, value);
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
}
|
||||
set allowPanMinScale(value) {
|
||||
this.setAttribute(allowPanMinScaleAttr, String(value));
|
||||
}
|
||||
get resetToMinScaleLimit() {
|
||||
const attrValue = this.getAttribute(resetToMinScaleLimitAttr);
|
||||
if (!attrValue)
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value))
|
||||
return Math.max(RESET_TO_MIN_SCALE_LIMIT, value);
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
}
|
||||
set resetToMinScaleLimit(value) {
|
||||
this.setAttribute(resetToMinScaleLimitAttr, String(value));
|
||||
}
|
||||
get stopPropagateHandled() {
|
||||
return this.hasAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
set stopPropagateHandled(value) {
|
||||
if (value) {
|
||||
this.setAttribute(stopPropagateHandledAttr, '');
|
||||
}
|
||||
else {
|
||||
this.removeAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
this._stageElChange();
|
||||
}
|
||||
|
@ -260,16 +322,24 @@ class PinchZoom extends HTMLElement {
|
|||
// ctrlKey is true when pinch-zooming on a trackpad.
|
||||
const divisor = ctrlKey ? 100 : 300;
|
||||
const scaleDiff = 1 - deltaY / divisor;
|
||||
const isZoomOut = scaleDiff < 1;
|
||||
this._applyChange({
|
||||
scaleDiff,
|
||||
originX: event.clientX - currentRect.left,
|
||||
originY: event.clientY - currentRect.top,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
if (isZoomOut) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
}
|
||||
_onPointerMove(previousPointers, currentPointers) {
|
||||
_onPointerMove(previousPointers, currentPointers, event) {
|
||||
if (!this._positioningEl)
|
||||
return;
|
||||
const isPan = previousPointers.length < 2;
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
// Combine next points with previous points
|
||||
const currentRect = this._positioningEl.getBoundingClientRect();
|
||||
// For calculating panning movement
|
||||
|
@ -288,6 +358,34 @@ class PinchZoom extends HTMLElement {
|
|||
panY: newMidpoint.clientY - prevMidpoint.clientY,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
_maybeResetScale() {
|
||||
if (roundedCmp(this.scale, this.resetToMinScaleLimit) <= 0) {
|
||||
this._resetToMinScale();
|
||||
}
|
||||
}
|
||||
_onPointerEnd(pointer, currentPointers, event) {
|
||||
if (!this._positioningEl)
|
||||
return;
|
||||
const totalPointers = 1 + currentPointers.length;
|
||||
const isPinch = totalPointers >= 2;
|
||||
const isPan = totalPointers == 1;
|
||||
if (isPinch) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
_resetToMinScale() {
|
||||
if (this.reachMinScaleStrategy === 'reset') {
|
||||
this.setTransform({ scale: this.minScale, x: 0, y: 0 });
|
||||
}
|
||||
else {
|
||||
this.setTransform({ scale: this.minScale });
|
||||
}
|
||||
}
|
||||
/** Transform the view & fire a change event */
|
||||
_applyChange(opts = {}) {
|
||||
|
@ -311,6 +409,29 @@ class PinchZoom extends HTMLElement {
|
|||
y: matrix.f,
|
||||
});
|
||||
}
|
||||
_maybeStopPropagate(event) {
|
||||
if (this.stopPropagateHandled) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
_allowPan() {
|
||||
return (this.allowPanMinScale > 0
|
||||
&& roundedCmp(this.scale, this.allowPanMinScale) > 0);
|
||||
}
|
||||
_maybeEmitCancel(pointers) {
|
||||
const makeCancelEvent = (pointer) => (new PointerEvent('pointercancel', {
|
||||
pointerId: pointer.id,
|
||||
clientX: pointer.clientX,
|
||||
clientY: pointer.clientY,
|
||||
}));
|
||||
if (this.stopPropagateHandled) {
|
||||
pointers.forEach(p => {
|
||||
if (this.parentElement && typeof this.parentElement.dispatchEvent === 'function') {
|
||||
this.parentElement.dispatchEvent(makeCancelEvent(p));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('pinch-zoom', PinchZoom);
|
||||
|
|
|
@ -30,6 +30,14 @@ interface SetTransformOpts extends ChangeOptions {
|
|||
type ScaleRelativeToValues = 'container' | 'content';
|
||||
|
||||
const minScaleAttr = 'min-scale';
|
||||
const allowPanMinScaleAttr = 'allow-pan-min-scale';
|
||||
const resetToMinScaleLimitAttr = 'reset-to-min-scale-limit';
|
||||
const reachMinScaleStrategyAttr = 'reach-min-scale-strategy';
|
||||
const stopPropagateHandledAttr = 'stop-propagate-handled';
|
||||
|
||||
type ReachMinScaleStrategy = 'reset' | 'none';
|
||||
|
||||
const reachMinScaleStrategyDefault: ReachMinScaleStrategy = 'none';
|
||||
|
||||
export interface ScaleToOpts extends ChangeOptions {
|
||||
/** Transform origin. Can be a number, or string percent, eg "50%" */
|
||||
|
@ -80,6 +88,12 @@ function createPoint(): SVGPoint {
|
|||
}
|
||||
|
||||
const MIN_SCALE = 0.01;
|
||||
const ALLOW_PAN_MIN_SCALE = -1;
|
||||
const RESET_TO_MIN_SCALE_LIMIT = -1;
|
||||
|
||||
const roundedCmp = (a: number, b: number) => {
|
||||
return Math.round(a * 100) - Math.round(b * 100)
|
||||
}
|
||||
|
||||
export default class PinchZoom extends HTMLElement {
|
||||
// The element that we'll transform.
|
||||
|
@ -105,11 +119,23 @@ export default class PinchZoom extends HTMLElement {
|
|||
start: (pointer, event) => {
|
||||
// We only want to track 2 pointers at most
|
||||
if (pointerTracker.currentPointers.length === 2 || !this._positioningEl) return false;
|
||||
|
||||
const isPan = pointerTracker.currentPointers.length + 1 === 1;
|
||||
|
||||
const handled = !(isPan && !this._allowPan());
|
||||
if (handled) {
|
||||
this._maybeStopPropagate(event);
|
||||
this._maybeEmitCancel([pointer, ...pointerTracker.currentPointers]);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
return true;
|
||||
},
|
||||
move: (previousPointers) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers);
|
||||
move: (previousPointers, _, event) => {
|
||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers, event);
|
||||
},
|
||||
end: (pointer, event) => {
|
||||
this._onPointerEnd(pointer, pointerTracker.currentPointers, event);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -138,6 +164,58 @@ export default class PinchZoom extends HTMLElement {
|
|||
this.setAttribute(minScaleAttr, String(value));
|
||||
}
|
||||
|
||||
get reachMinScaleStrategy(): ReachMinScaleStrategy {
|
||||
const attrValue = this.getAttribute(reachMinScaleStrategyAttr);
|
||||
|
||||
const v = attrValue as ReachMinScaleStrategy;
|
||||
|
||||
return v || reachMinScaleStrategyDefault;
|
||||
}
|
||||
|
||||
set reachMinScaleStrategy(value: ReachMinScaleStrategy) {
|
||||
this.setAttribute(reachMinScaleStrategyAttr, value as string);
|
||||
}
|
||||
|
||||
get allowPanMinScale(): number {
|
||||
const attrValue = this.getAttribute(allowPanMinScaleAttr);
|
||||
if (!attrValue) return ALLOW_PAN_MIN_SCALE;
|
||||
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value)) return Math.max(ALLOW_PAN_MIN_SCALE, value);
|
||||
|
||||
return ALLOW_PAN_MIN_SCALE;
|
||||
}
|
||||
|
||||
set allowPanMinScale(value: number) {
|
||||
this.setAttribute(allowPanMinScaleAttr, String(value));
|
||||
}
|
||||
|
||||
get resetToMinScaleLimit(): number {
|
||||
const attrValue = this.getAttribute(resetToMinScaleLimitAttr);
|
||||
if (!attrValue) return RESET_TO_MIN_SCALE_LIMIT;
|
||||
|
||||
const value = parseFloat(attrValue);
|
||||
if (Number.isFinite(value)) return Math.max(RESET_TO_MIN_SCALE_LIMIT, value);
|
||||
|
||||
return RESET_TO_MIN_SCALE_LIMIT;
|
||||
}
|
||||
|
||||
set resetToMinScaleLimit(value: number) {
|
||||
this.setAttribute(resetToMinScaleLimitAttr, String(value));
|
||||
}
|
||||
|
||||
get stopPropagateHandled() {
|
||||
return this.hasAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
|
||||
set stopPropagateHandled(value: boolean) {
|
||||
if (value) {
|
||||
this.setAttribute(stopPropagateHandledAttr, '');
|
||||
} else {
|
||||
this.removeAttribute(stopPropagateHandledAttr);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this._stageElChange();
|
||||
}
|
||||
|
@ -331,17 +409,29 @@ export default class PinchZoom extends HTMLElement {
|
|||
const divisor = ctrlKey ? 100 : 300;
|
||||
const scaleDiff = 1 - deltaY / divisor;
|
||||
|
||||
const isZoomOut = scaleDiff < 1;
|
||||
|
||||
this._applyChange({
|
||||
scaleDiff,
|
||||
originX: event.clientX - currentRect.left,
|
||||
originY: event.clientY - currentRect.top,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
|
||||
if (isZoomOut) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
}
|
||||
|
||||
private _onPointerMove(previousPointers: Pointer[], currentPointers: Pointer[]) {
|
||||
private _onPointerMove(previousPointers: Pointer[], currentPointers: Pointer[], event: Event) {
|
||||
if (!this._positioningEl) return;
|
||||
|
||||
const isPan = previousPointers.length < 2;
|
||||
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Combine next points with previous points
|
||||
const currentRect = this._positioningEl.getBoundingClientRect();
|
||||
|
||||
|
@ -364,8 +454,42 @@ export default class PinchZoom extends HTMLElement {
|
|||
panY: newMidpoint.clientY - prevMidpoint.clientY,
|
||||
allowChangeEvent: true,
|
||||
});
|
||||
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
|
||||
private _maybeResetScale() {
|
||||
if (roundedCmp(this.scale, this.resetToMinScaleLimit) <= 0) {
|
||||
this._resetToMinScale();
|
||||
}
|
||||
}
|
||||
|
||||
private _onPointerEnd(pointer: Pointer, currentPointers: Pointer[], event: Event) {
|
||||
if (!this._positioningEl) return;
|
||||
|
||||
const totalPointers = 1 + currentPointers.length;
|
||||
const isPinch = totalPointers >= 2;
|
||||
const isPan = totalPointers == 1;
|
||||
|
||||
if (isPinch) {
|
||||
this._maybeResetScale();
|
||||
}
|
||||
|
||||
if (isPan && !this._allowPan()) {
|
||||
return;
|
||||
}
|
||||
this._maybeStopPropagate(event);
|
||||
}
|
||||
|
||||
private _resetToMinScale() {
|
||||
if (this.reachMinScaleStrategy === 'reset') {
|
||||
this.setTransform({ scale: this.minScale, x: 0, y: 0 });
|
||||
} else {
|
||||
this.setTransform({ scale: this.minScale });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Transform the view & fire a change event */
|
||||
private _applyChange(opts: ApplyChangeOpts = {}) {
|
||||
const {
|
||||
|
@ -395,4 +519,35 @@ export default class PinchZoom extends HTMLElement {
|
|||
y: matrix.f,
|
||||
});
|
||||
}
|
||||
|
||||
private _maybeStopPropagate(event: Event) {
|
||||
if (this.stopPropagateHandled) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private _allowPan() {
|
||||
return (
|
||||
this.allowPanMinScale > 0
|
||||
&& roundedCmp(this.scale, this.allowPanMinScale) > 0
|
||||
);
|
||||
}
|
||||
|
||||
private _maybeEmitCancel(pointers: Pointer[]) {
|
||||
const makeCancelEvent = (pointer: Pointer) => (
|
||||
new PointerEvent(
|
||||
'pointercancel', {
|
||||
pointerId: pointer.id,
|
||||
clientX: pointer.clientX,
|
||||
clientY: pointer.clientY,
|
||||
}
|
||||
));
|
||||
if (this.stopPropagateHandled) {
|
||||
pointers.forEach(p => {
|
||||
if (this.parentElement && typeof this.parentElement.dispatchEvent === 'function') {
|
||||
this.parentElement.dispatchEvent(makeCancelEvent(p));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"module": "dist/pinch-zoom.es.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "rm -r dist && rollup -c"
|
||||
"build": "rm -rf dist && rollup -c"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
Loading…
Add table
Reference in a new issue