type DragCallback = (dx: number, dy: number) => void type ZoomCallback = (scale: number, focusX: number, focusY: number) => void type TapCallback = (dx: number, dy: number) => void export interface Callbacks { drag?: DragCallback, zoom?: ZoomCallback, tap?: TapCallback, } interface MyTouch { identifier: number, lastX: number, lastY: number, dx: number, dy: number, } interface ScaleTracker { focusX: number, focusY: number, distance: number, } export class TouchTracker { touches: Array = [] callbacks: Callbacks; el: HTMLElement; scaleTracker?: ScaleTracker distance = 0 constructor(el: HTMLElement, callbacks: Callbacks) { this.callbacks = callbacks; this.el = el; } private removeTouch(identifier: number) { let index = this.touches.findIndex(t => t.identifier == identifier) if (index >= 0) { this.touches.splice(index, 1) } } private getTouch(identifier: number): MyTouch | null { let index = this.touches.findIndex(t => t.identifier == identifier) if (index >= 0) return this.touches[index] else return null } private getX(touch: Touch) { //return touch.pageX - this.el.offsetLeft return touch.pageX } private getY(touch: Touch) { //return touch.pageY - this.el.offsetTop return touch.pageY } start(list: TouchList) { for (var i = 0; i < list.length; i++) { let touch = list[i]; this.removeTouch(touch.identifier); this.touches.push({ lastX: this.getX(touch), lastY: this.getY(touch), dx: 0, dy: 0, identifier: touch.identifier }) } if (this.touches.length == 1) { this.distance = 0; } this.updateFocus(); } updateFocus() { if (this.touches.length < 2) { this.scaleTracker = undefined; return } let t1 = this.touches[0]; let t2 = this.touches[1]; let focusX = (t1.lastX + t2.lastX) / 2; let focusY = (t1.lastY + t2.lastY) / 2; let distance = Math.sqrt(Math.pow(t2.lastX - t1.lastX, 2) + Math.pow(t2.lastY - t1.lastY, 2)); this.scaleTracker = { focusX, focusY, distance } } end(list: TouchList) { console.log('end') for (var i = 0; i < list.length; i++) { this.removeTouch(list[i].identifier); } this.updateFocus(); if (this.touches.length <= 0 && this.distance == 0) { let touch = list[0]; let el = touch.target as HTMLElement //console.log('kkkkkk', el.offsetLeft, el.offsetTop) //this.callbacks?.tap?.(touch.clientX - el.offsetLeft, touch.clientY - el.offsetTop) this.callbacks?.tap?.(touch.clientX , touch.clientY) } } move(list: TouchList) { for (var i = 0; i < list.length; i++) { let touch = list[i]; let mytouch = this.getTouch(touch.identifier); if (mytouch == null) continue; mytouch.dx = this.getX(touch) - mytouch.lastX mytouch.dy = this.getY(touch) - mytouch.lastY mytouch.lastX = this.getX(touch) mytouch.lastY = this.getY(touch) if (this.scaleTracker) { let t1 = this.touches[0]; let t2 = this.touches[1]; let focusX = (t1.lastX + t2.lastX) / 2; let focusY = (t1.lastY + t2.lastY) / 2; let distance = Math.sqrt(Math.pow(t2.lastX - t1.lastX, 2) + Math.pow(t2.lastY - t1.lastY, 2)); let dx = focusX - this.scaleTracker.focusX; let dy = focusY - this.scaleTracker.focusY; let scale = distance / this.scaleTracker.distance; console.log(`dx=${dx}, dy=${dy}, scale=${scale}, distance=${distance}`); this.callbacks.drag?.(dx, dy); this.callbacks.zoom?.(scale, focusX, focusY); this.scaleTracker.focusX = focusX; this.scaleTracker.focusY = focusY; this.scaleTracker.distance = distance; this.distance += Math.abs(dx) + Math.abs(dy) } else { //if(mytouch.dx > 0 || mytouch.dy >0) { this.callbacks.drag?.(mytouch.dx, mytouch.dy) // } this.distance += Math.abs(mytouch.dx) + Math.abs(mytouch.dy) } } } } export class Gesture { constructor(el: HTMLElement, callbacks: Callbacks) { var dx, dy; var isDown = false; var lastX: number = 0; var lastY: number = 0; var distance = 0; el.addEventListener('mousedown', e => { isDown = true; lastX = e.clientX; lastY = e.clientY; distance = 0; }) document.addEventListener('mouseup', e => { isDown = false; if (distance == 0 && e.target == el) { callbacks.tap?.(e.offsetX, e.offsetY) } }) document.addEventListener('mousemove', e => { if (isDown) { e.preventDefault() dx = e.clientX - lastX; dy = e.clientY - lastY; lastX = e.clientX; lastY = e.clientY; distance += Math.abs(dx) + Math.abs(dy) callbacks?.drag?.(dx, dy); } }) el.addEventListener('wheel', e => { e.preventDefault() let scale = e.deltaY * -0.01 + 1; callbacks.zoom?.(scale, e.offsetX, e.offsetY) }) el.addEventListener('click', e => { e.preventDefault() //callbacks.tap?.(e.offsetX, e.offsetY) }) const tracker = new TouchTracker(el, callbacks); el.addEventListener('touchstart', e => { e.preventDefault(); tracker.start(e.changedTouches); }) el.addEventListener('touchmove', e => { tracker.move(e.changedTouches) }) el.addEventListener('touchend', e => { tracker.end(e.changedTouches) }) el.addEventListener('touchcancel', e => { tracker.end(e.changedTouches) }) } }