pinch-to-zoom
Version:
Pinch to zoom
135 lines (110 loc) • 4.53 kB
text/typescript
import {
getLengthOfLine,
getMiddleTouchOnElement,
isTouchesInsideRect,
ITouchCoordinates,
} from './helpers';
export interface IPinchToZoomConfig {
maxScale: number;
minScale: number;
transitionDuration: number;
onScale?: (scale: number, translateX: number, translateY: number) => any;
}
let defaultConfig: IPinchToZoomConfig = {
maxScale: 4,
minScale: 1,
transitionDuration: 100,
};
export class PinchToZoom {
private firstTouch: ITouchCoordinates;
private initialPinchLength: number;
private currentPinchLength: number;
private initialBoundingRect: ClientRect | DOMRect;
private timeoutId: number;
private config: IPinchToZoomConfig;
constructor(
private element: HTMLElement,
config: IPinchToZoomConfig = defaultConfig,
) {
this.config = {
...defaultConfig,
...config,
};
element.addEventListener('touchstart', this.onTouchStart);
element.addEventListener('touchmove', this.onTouchMove);
element.addEventListener('touchend', this.onTouchEnd);
element.addEventListener('touchcancel', this.onTouchEnd);
}
public unsubscribe = (): void => {
clearTimeout(this.timeoutId);
this.element.removeEventListener('touchstart', this.onTouchStart);
this.element.removeEventListener('touchmove', this.onTouchMove);
this.element.removeEventListener('touchend', this.onTouchEnd);
this.element.removeEventListener('touchcancel', this.onTouchEnd);
}
private onTouchStart = (event: TouchEvent): void => {
if (event.touches.length !== 2) {
return;
}
this.initialBoundingRect = this.element.getBoundingClientRect();
if (!event.touches.length || !isTouchesInsideRect(event.touches, this.initialBoundingRect)) {
return;
}
event.preventDefault();
this.setTransitionDuration(0);
let [touch1, touch2] = event.touches;
let middleTouchOnElement: ITouchCoordinates = getMiddleTouchOnElement(
event.touches,
this.initialBoundingRect,
);
this.initialPinchLength = getLengthOfLine(touch1, touch2);
this.setTransformOrigin(`${middleTouchOnElement.clientX}px`, `${middleTouchOnElement.clientY}px`);
this.firstTouch = middleTouchOnElement;
}
private onTouchMove = (event: TouchEvent): void => {
if (this.firstTouch) {
let middleTouchOnElement: ITouchCoordinates = getMiddleTouchOnElement(
event.touches,
this.initialBoundingRect,
);
this.currentPinchLength = getLengthOfLine(event.touches[0], event.touches[1]);
let scale: number = this.currentPinchLength / this.initialPinchLength;
let translateX: number = middleTouchOnElement.clientX - this.firstTouch.clientX;
let translateY: number = middleTouchOnElement.clientY - this.firstTouch.clientY;
this.setTransform(scale, translateX, translateY);
if (this.config.onScale) {
this.config.onScale(scale, translateX, translateY);
}
}
}
private onTouchEnd = (): void => {
this.setTransitionDuration(this.config.transitionDuration);
this.setTransform(1, 0, 0);
clearTimeout(this.timeoutId);
this.timeoutId = window.setTimeout(() => {
this.setTransitionDuration(0);
this.setTransformOrigin('50%', '50%');
}, this.config.transitionDuration);
this.resetProperties();
}
private resetProperties = (): void => {
this.firstTouch = null;
this.initialPinchLength = null;
this.currentPinchLength = null;
this.initialBoundingRect = null;
}
private setTransitionDuration = (duration: number): void => {
this.element.style.transitionDuration = `${duration}ms`;
}
private setTransform = (scale: number, translateX: number, translateY: number): void => {
scale = scale < this.config.minScale ? this.config.minScale : scale;
scale = scale > this.config.maxScale ? this.config.maxScale : scale;
this.element.style.transform = (
`translate3d(${translateX}px, ${translateY}px, 0)` +
`scale3d(${scale}, ${scale}, 1)`
);
}
private setTransformOrigin = (x: string, y: string): void => {
this.element.style.transformOrigin = `${x} ${y} 0`;
}
}