mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
perf(menu): flickering
This commit is contained in:
@@ -339,10 +339,8 @@ export class Animator {
|
||||
* Play the animation.
|
||||
*/
|
||||
play(opts?: PlayOptions) {
|
||||
const self = this;
|
||||
|
||||
// If the animation was already invalidated (it did finish), do nothing
|
||||
if (self._destroyed) {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -352,14 +350,14 @@ export class Animator {
|
||||
// if there is a duration, then it'll stage all animations at the
|
||||
// FROM property and transition duration, wait a few frames, then
|
||||
// kick off the animation by setting the TO property for each animation
|
||||
self._isAsync = self._hasDuration(opts);
|
||||
this._isAsync = this._hasDuration(opts);
|
||||
|
||||
// ensure all past transition end events have been cleared
|
||||
self._clearAsync();
|
||||
this._clearAsync();
|
||||
|
||||
// recursively kicks off the correct progress step for each child animation
|
||||
// ******** DOM WRITE ****************
|
||||
self._playInit(opts);
|
||||
this._playInit(opts);
|
||||
|
||||
// doubling up RAFs since this animation was probably triggered
|
||||
// from an input event, and just having one RAF would have this code
|
||||
@@ -367,7 +365,7 @@ export class Animator {
|
||||
// input event probably already did way too much work for one frame
|
||||
raf(() => {
|
||||
raf(() => {
|
||||
self._playDomInspect(opts);
|
||||
this._playDomInspect(opts);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -430,30 +428,29 @@ export class Animator {
|
||||
* ROOT ANIMATION
|
||||
*/
|
||||
_playDomInspect(opts: PlayOptions | undefined) {
|
||||
const self = this;
|
||||
// fire off all the "before" function that have DOM READS in them
|
||||
// elements will be in the DOM, however visibily hidden
|
||||
// so we can read their dimensions if need be
|
||||
// ******** DOM READ ****************
|
||||
// ******** DOM WRITE ****************
|
||||
self._beforeAnimation();
|
||||
this._beforeAnimation();
|
||||
|
||||
// for the root animation only
|
||||
// set the async TRANSITION END event
|
||||
// and run onFinishes when the transition ends
|
||||
const dur = self.getDuration(opts);
|
||||
if (self._isAsync) {
|
||||
self._asyncEnd(dur, true);
|
||||
const dur = this.getDuration(opts);
|
||||
if (this._isAsync) {
|
||||
this._asyncEnd(dur, true);
|
||||
}
|
||||
|
||||
// ******** DOM WRITE ****************
|
||||
self._playProgress(opts);
|
||||
this._playProgress(opts);
|
||||
|
||||
if (self._isAsync && !this._destroyed) {
|
||||
if (this._isAsync && !this._destroyed) {
|
||||
// this animation has a duration so we need another RAF
|
||||
// for the CSS TRANSITION properties to kick in
|
||||
raf(() => {
|
||||
self._playToStep(1);
|
||||
this._playToStep(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -721,7 +718,7 @@ export class Animator {
|
||||
} else {
|
||||
for (j = 0; j < elements.length; j++) {
|
||||
// ******** DOM WRITE ****************
|
||||
(elements[j].style as any)[prop] = val;
|
||||
elements[j].style.setProperty(prop, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -759,7 +756,6 @@ export class Animator {
|
||||
for (const { style } of elements) {
|
||||
if (dur > 0) {
|
||||
// ******** DOM WRITE ****************
|
||||
style.transform = '';
|
||||
style.transitionDuration = durString;
|
||||
|
||||
// each animation can have a different easing
|
||||
@@ -768,7 +764,7 @@ export class Animator {
|
||||
style.transitionTimingFunction = easing;
|
||||
}
|
||||
} else {
|
||||
style.transform = 'none';
|
||||
style.transitionDuration = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +68,7 @@ ion-backdrop {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.menu-content-open ion-pane,
|
||||
.menu-content-open .ion-pane,
|
||||
.menu-content-open ion-content,
|
||||
.menu-content-open .toolbar {
|
||||
.menu-content-open {
|
||||
// the containing element itself should be clickable but
|
||||
// everything inside of it should not clickable when menu is open
|
||||
pointer-events: none;
|
||||
|
||||
@@ -1,52 +1,8 @@
|
||||
import { QueueApi } from '@stencil/core';
|
||||
|
||||
import { PanRecognizer } from './recognizers';
|
||||
|
||||
import { gestureController } from './gesture-controller';
|
||||
import { PointerEvents } from './pointer-events';
|
||||
|
||||
export interface GestureDetail {
|
||||
type: string;
|
||||
startX: number;
|
||||
startY: number;
|
||||
startTimeStamp: number;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
velocityX: number;
|
||||
velocityY: number;
|
||||
deltaX: number;
|
||||
deltaY: number;
|
||||
timeStamp: number;
|
||||
event: UIEvent;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export type GestureCallback = (detail?: GestureDetail) => boolean | void;
|
||||
|
||||
export interface Gesture {
|
||||
setDisabled(disabled: boolean): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export interface GestureConfig {
|
||||
el: Node;
|
||||
disableScroll?: boolean;
|
||||
|
||||
queue: QueueApi;
|
||||
direction?: 'x' | 'y';
|
||||
gestureName: string;
|
||||
gesturePriority?: number;
|
||||
passive?: boolean;
|
||||
maxAngle?: number;
|
||||
threshold?: number;
|
||||
|
||||
canStart?: GestureCallback;
|
||||
onWillStart?: (_: GestureDetail) => Promise<void>;
|
||||
onStart?: GestureCallback;
|
||||
onMove?: GestureCallback;
|
||||
onEnd?: GestureCallback;
|
||||
notCaptured?: GestureCallback;
|
||||
}
|
||||
import { createPointerEvents } from './pointer-events';
|
||||
import { createPanRecognizer } from './recognizers';
|
||||
|
||||
export function createGesture(config: GestureConfig): Gesture {
|
||||
const finalConfig = {
|
||||
@@ -60,11 +16,6 @@ export function createGesture(config: GestureConfig): Gesture {
|
||||
...config
|
||||
};
|
||||
|
||||
let hasCapturedPan = false;
|
||||
let hasStartedPan = false;
|
||||
let hasFiredStart = true;
|
||||
let isMoveQueued = false;
|
||||
|
||||
const canStart = finalConfig.canStart;
|
||||
const onWillStart = finalConfig.onWillStart;
|
||||
const onStart = finalConfig.onStart;
|
||||
@@ -90,7 +41,7 @@ export function createGesture(config: GestureConfig): Gesture {
|
||||
data: undefined
|
||||
};
|
||||
|
||||
const pointerEvents = new PointerEvents(
|
||||
const pointerEvents = createPointerEvents(
|
||||
finalConfig.el,
|
||||
pointerDown,
|
||||
pointerMove,
|
||||
@@ -100,13 +51,18 @@ export function createGesture(config: GestureConfig): Gesture {
|
||||
}
|
||||
);
|
||||
|
||||
const pan = new PanRecognizer(finalConfig.direction, finalConfig.threshold, finalConfig.maxAngle);
|
||||
const pan = createPanRecognizer(finalConfig.direction, finalConfig.threshold, finalConfig.maxAngle);
|
||||
const gesture = gestureController.createGesture({
|
||||
name: config.gestureName,
|
||||
priority: config.gesturePriority,
|
||||
disableScroll: config.disableScroll
|
||||
});
|
||||
|
||||
let hasCapturedPan = false;
|
||||
let hasStartedPan = false;
|
||||
let hasFiredStart = true;
|
||||
let isMoveQueued = false;
|
||||
|
||||
function pointerDown(ev: UIEvent): boolean {
|
||||
const timeStamp = now(ev);
|
||||
if (hasStartedPan || !hasFiredStart) {
|
||||
@@ -146,14 +102,14 @@ export function createGesture(config: GestureConfig): Gesture {
|
||||
if (hasCapturedPan) {
|
||||
if (!isMoveQueued && hasFiredStart) {
|
||||
isMoveQueued = true;
|
||||
calcGestureData(ev);
|
||||
calcGestureData(detail, ev);
|
||||
queue.write(fireOnMove);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// gesture is currently being detected
|
||||
calcGestureData(ev);
|
||||
calcGestureData(detail, ev);
|
||||
if (pan.detect(detail.currentX, detail.currentY)) {
|
||||
if (pan.isGesture()) {
|
||||
if (!tryToCapturePan()) {
|
||||
@@ -175,28 +131,6 @@ export function createGesture(config: GestureConfig): Gesture {
|
||||
}
|
||||
}
|
||||
|
||||
function calcGestureData(ev: UIEvent) {
|
||||
const prevX = detail.currentX;
|
||||
const prevY = detail.currentY;
|
||||
const prevT = detail.timeStamp;
|
||||
|
||||
updateDetail(ev, detail);
|
||||
|
||||
const currentX = detail.currentX;
|
||||
const currentY = detail.currentY;
|
||||
const timestamp = detail.timeStamp = now(ev);
|
||||
const timeDelta = timestamp - prevT;
|
||||
if (timeDelta > 0 && timeDelta < 100) {
|
||||
const velocityX = (currentX - prevX) / timeDelta;
|
||||
const velocityY = (currentY - prevY) / timeDelta;
|
||||
detail.velocityX = velocityX * 0.7 + detail.velocityX * 0.3;
|
||||
detail.velocityY = velocityY * 0.7 + detail.velocityY * 0.3;
|
||||
}
|
||||
detail.deltaX = currentX - detail.startX;
|
||||
detail.deltaY = currentY - detail.startY;
|
||||
detail.event = ev;
|
||||
}
|
||||
|
||||
function tryToCapturePan(): boolean {
|
||||
if (gesture && !gesture.capture()) {
|
||||
return false;
|
||||
@@ -256,7 +190,7 @@ export function createGesture(config: GestureConfig): Gesture {
|
||||
if (!tmpHasFiredStart) {
|
||||
return;
|
||||
}
|
||||
calcGestureData(ev);
|
||||
calcGestureData(detail, ev);
|
||||
|
||||
// Try to capture press
|
||||
if (tmpHasCaptured) {
|
||||
@@ -274,7 +208,7 @@ export function createGesture(config: GestureConfig): Gesture {
|
||||
|
||||
return {
|
||||
setDisabled(disabled: boolean) {
|
||||
pointerEvents.disabled = disabled;
|
||||
pointerEvents.setDisabled(disabled);
|
||||
},
|
||||
destroy() {
|
||||
gesture.destroy();
|
||||
@@ -283,6 +217,28 @@ export function createGesture(config: GestureConfig): Gesture {
|
||||
};
|
||||
}
|
||||
|
||||
function calcGestureData(detail: GestureDetail, ev: UIEvent) {
|
||||
const prevX = detail.currentX;
|
||||
const prevY = detail.currentY;
|
||||
const prevT = detail.timeStamp;
|
||||
|
||||
updateDetail(ev, detail);
|
||||
|
||||
const currentX = detail.currentX;
|
||||
const currentY = detail.currentY;
|
||||
const timestamp = detail.timeStamp = now(ev);
|
||||
const timeDelta = timestamp - prevT;
|
||||
if (timeDelta > 0 && timeDelta < 100) {
|
||||
const velocityX = (currentX - prevX) / timeDelta;
|
||||
const velocityY = (currentY - prevY) / timeDelta;
|
||||
detail.velocityX = velocityX * 0.7 + detail.velocityX * 0.3;
|
||||
detail.velocityY = velocityY * 0.7 + detail.velocityY * 0.3;
|
||||
}
|
||||
detail.deltaX = currentX - detail.startX;
|
||||
detail.deltaY = currentY - detail.startY;
|
||||
detail.event = ev;
|
||||
}
|
||||
|
||||
function updateDetail(ev: any, detail: GestureDetail) {
|
||||
// get X coordinates for either a mouse click
|
||||
// or a touch depending on the given event
|
||||
@@ -306,3 +262,46 @@ function updateDetail(ev: any, detail: GestureDetail) {
|
||||
function now(ev: UIEvent) {
|
||||
return ev.timeStamp || Date.now();
|
||||
}
|
||||
|
||||
export interface GestureDetail {
|
||||
type: string;
|
||||
startX: number;
|
||||
startY: number;
|
||||
startTimeStamp: number;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
velocityX: number;
|
||||
velocityY: number;
|
||||
deltaX: number;
|
||||
deltaY: number;
|
||||
timeStamp: number;
|
||||
event: UIEvent;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export type GestureCallback = (detail?: GestureDetail) => boolean | void;
|
||||
|
||||
export interface Gesture {
|
||||
setDisabled(disabled: boolean): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export interface GestureConfig {
|
||||
el: Node;
|
||||
disableScroll?: boolean;
|
||||
|
||||
queue: QueueApi;
|
||||
direction?: 'x' | 'y';
|
||||
gestureName: string;
|
||||
gesturePriority?: number;
|
||||
passive?: boolean;
|
||||
maxAngle?: number;
|
||||
threshold?: number;
|
||||
|
||||
canStart?: GestureCallback;
|
||||
onWillStart?: (_: GestureDetail) => Promise<void>;
|
||||
onStart?: GestureCallback;
|
||||
onMove?: GestureCallback;
|
||||
onEnd?: GestureCallback;
|
||||
notCaptured?: GestureCallback;
|
||||
}
|
||||
|
||||
@@ -2,141 +2,134 @@ import { addEventListener } from './listener';
|
||||
|
||||
const MOUSE_WAIT = 2000;
|
||||
|
||||
export class PointerEvents {
|
||||
export function createPointerEvents(
|
||||
el: Node,
|
||||
pointerDown: any,
|
||||
pointerMove: any,
|
||||
pointerUp: any,
|
||||
options: EventListenerOptions
|
||||
) {
|
||||
|
||||
private rmTouchStart?: () => void;
|
||||
private rmTouchMove?: () => void;
|
||||
private rmTouchEnd?: () => void;
|
||||
private rmTouchCancel?: () => void;
|
||||
let rmTouchStart: (() => void) | undefined;
|
||||
let rmTouchMove: (() => void) | undefined;
|
||||
let rmTouchEnd: (() => void) | undefined;
|
||||
let rmTouchCancel: (() => void) | undefined;
|
||||
let rmMouseStart: (() => void) | undefined;
|
||||
let rmMouseMove: (() => void) | undefined;
|
||||
let rmMouseUp: (() => void) | undefined;
|
||||
let lastTouchEvent = 0;
|
||||
|
||||
private rmMouseStart?: () => void;
|
||||
private rmMouseMove?: () => void;
|
||||
private rmMouseUp?: () => void;
|
||||
|
||||
private bindTouchEnd: any;
|
||||
private bindMouseUp: any;
|
||||
|
||||
private lastTouchEvent = 0;
|
||||
|
||||
constructor(
|
||||
private el: Node,
|
||||
private pointerDown: any,
|
||||
private pointerMove: any,
|
||||
private pointerUp: any,
|
||||
private options: EventListenerOptions
|
||||
) {
|
||||
this.bindTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.bindMouseUp = this.handleMouseUp.bind(this);
|
||||
}
|
||||
|
||||
set disabled(disabled: boolean) {
|
||||
if (disabled) {
|
||||
if (this.rmTouchStart) {
|
||||
this.rmTouchStart();
|
||||
}
|
||||
if (this.rmMouseStart) {
|
||||
this.rmMouseStart();
|
||||
}
|
||||
this.rmTouchStart = this.rmMouseStart = undefined;
|
||||
this.stop();
|
||||
|
||||
} else {
|
||||
if (!this.rmTouchStart) {
|
||||
this.rmTouchStart = addEventListener(this.el, 'touchstart', this.handleTouchStart.bind(this), this.options);
|
||||
}
|
||||
if (!this.rmMouseStart) {
|
||||
this.rmMouseStart = addEventListener(this.el, 'mousedown', this.handleMouseDown.bind(this), this.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.stopTouch();
|
||||
this.stopMouse();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.disabled = true;
|
||||
this.pointerUp = this.pointerMove = this.pointerDown = undefined;
|
||||
}
|
||||
|
||||
private handleTouchStart(ev: any) {
|
||||
this.lastTouchEvent = Date.now() + MOUSE_WAIT;
|
||||
if (!this.pointerDown(ev, POINTER_EVENT_TYPE_TOUCH)) {
|
||||
function handleTouchStart(ev: any) {
|
||||
lastTouchEvent = Date.now() + MOUSE_WAIT;
|
||||
if (!pointerDown(ev)) {
|
||||
return;
|
||||
}
|
||||
if (!this.rmTouchMove && this.pointerMove) {
|
||||
this.rmTouchMove = addEventListener(this.el, 'touchmove', this.pointerMove, this.options);
|
||||
if (!rmTouchMove && pointerMove) {
|
||||
rmTouchMove = addEventListener(el, 'touchmove', pointerMove, options);
|
||||
}
|
||||
if (!this.rmTouchEnd) {
|
||||
this.rmTouchEnd = addEventListener(this.el, 'touchend', this.bindTouchEnd, this.options);
|
||||
if (!rmTouchEnd) {
|
||||
rmTouchEnd = addEventListener(el, 'touchend', handleTouchEnd, options);
|
||||
}
|
||||
if (!this.rmTouchCancel) {
|
||||
this.rmTouchCancel = addEventListener(this.el, 'touchcancel', this.bindTouchEnd, this.options);
|
||||
if (!rmTouchCancel) {
|
||||
rmTouchCancel = addEventListener(el, 'touchcancel', handleTouchEnd, options);
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseDown(ev: any) {
|
||||
if (this.lastTouchEvent > Date.now()) {
|
||||
function handleMouseDown(ev: any) {
|
||||
if (lastTouchEvent > Date.now()) {
|
||||
console.debug('mousedown event dropped because of previous touch');
|
||||
return;
|
||||
}
|
||||
if (!this.pointerDown(ev, POINTER_EVENT_TYPE_MOUSE)) {
|
||||
if (!pointerDown(ev)) {
|
||||
return;
|
||||
}
|
||||
if (!this.rmMouseMove && this.pointerMove) {
|
||||
this.rmMouseMove = addEventListener(getDocument(this.el), 'mousemove', this.pointerMove, this.options);
|
||||
if (!rmMouseMove && pointerMove) {
|
||||
rmMouseMove = addEventListener(getDocument(el), 'mousemove', pointerMove, options);
|
||||
}
|
||||
if (!this.rmMouseUp) {
|
||||
this.rmMouseUp = addEventListener(getDocument(this.el), 'mouseup', this.bindMouseUp, this.options);
|
||||
if (!rmMouseUp) {
|
||||
rmMouseUp = addEventListener(getDocument(el), 'mouseup', handleMouseUp, options);
|
||||
}
|
||||
}
|
||||
|
||||
private handleTouchEnd(ev: any) {
|
||||
this.stopTouch();
|
||||
if (this.pointerUp) {
|
||||
this.pointerUp(ev, POINTER_EVENT_TYPE_TOUCH);
|
||||
function handleTouchEnd(ev: any) {
|
||||
stopTouch();
|
||||
if (pointerUp) {
|
||||
pointerUp(ev);
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseUp(ev: any) {
|
||||
this.stopMouse();
|
||||
if (this.pointerUp) {
|
||||
this.pointerUp(ev, POINTER_EVENT_TYPE_MOUSE);
|
||||
function handleMouseUp(ev: any) {
|
||||
stopMouse();
|
||||
if (pointerUp) {
|
||||
pointerUp(ev);
|
||||
}
|
||||
}
|
||||
|
||||
private stopTouch() {
|
||||
if (this.rmTouchMove) {
|
||||
this.rmTouchMove();
|
||||
function stopTouch() {
|
||||
if (rmTouchMove) {
|
||||
rmTouchMove();
|
||||
}
|
||||
if (this.rmTouchEnd) {
|
||||
this.rmTouchEnd();
|
||||
if (rmTouchEnd) {
|
||||
rmTouchEnd();
|
||||
}
|
||||
if (this.rmTouchCancel) {
|
||||
this.rmTouchCancel();
|
||||
if (rmTouchCancel) {
|
||||
rmTouchCancel();
|
||||
}
|
||||
this.rmTouchMove = this.rmTouchEnd = this.rmTouchCancel = undefined;
|
||||
rmTouchMove = rmTouchEnd = rmTouchCancel = undefined;
|
||||
}
|
||||
|
||||
private stopMouse() {
|
||||
if (this.rmMouseMove) {
|
||||
this.rmMouseMove();
|
||||
function stopMouse() {
|
||||
if (rmMouseMove) {
|
||||
rmMouseMove();
|
||||
}
|
||||
if (this.rmMouseUp) {
|
||||
this.rmMouseUp();
|
||||
if (rmMouseUp) {
|
||||
rmMouseUp();
|
||||
}
|
||||
this.rmMouseMove = this.rmMouseUp = undefined;
|
||||
rmMouseMove = rmMouseUp = undefined;
|
||||
}
|
||||
|
||||
function stop() {
|
||||
stopTouch();
|
||||
stopMouse();
|
||||
}
|
||||
|
||||
function setDisabled(disabled: boolean) {
|
||||
if (disabled) {
|
||||
if (rmTouchStart) {
|
||||
rmTouchStart();
|
||||
}
|
||||
if (rmMouseStart) {
|
||||
rmMouseStart();
|
||||
}
|
||||
rmTouchStart = rmMouseStart = undefined;
|
||||
stop();
|
||||
|
||||
} else {
|
||||
if (!rmTouchStart) {
|
||||
rmTouchStart = addEventListener(el, 'touchstart', handleTouchStart, options);
|
||||
}
|
||||
if (!rmMouseStart) {
|
||||
rmMouseStart = addEventListener(el, 'mousedown', handleMouseDown, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
setDisabled(true);
|
||||
pointerUp = pointerMove = pointerDown = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
setDisabled,
|
||||
stop,
|
||||
destroy
|
||||
};
|
||||
}
|
||||
|
||||
function getDocument(node: Node) {
|
||||
return node instanceof Document ? node : node.ownerDocument;
|
||||
}
|
||||
|
||||
export const POINTER_EVENT_TYPE_MOUSE = 1;
|
||||
export const POINTER_EVENT_TYPE_TOUCH = 2;
|
||||
|
||||
export interface PointerEventsConfig {
|
||||
element?: HTMLElement;
|
||||
pointerDown: (ev: any) => boolean;
|
||||
|
||||
@@ -1,63 +1,66 @@
|
||||
|
||||
export class PanRecognizer {
|
||||
|
||||
private startX!: number;
|
||||
private startY!: number;
|
||||
|
||||
private dirty = false;
|
||||
private threshold: number;
|
||||
private maxCosine: number;
|
||||
private isDirX: boolean;
|
||||
private isPan = 0;
|
||||
|
||||
constructor(direction: string, threshold: number, maxAngle: number) {
|
||||
const radians = maxAngle * (Math.PI / 180);
|
||||
this.isDirX = direction === 'x';
|
||||
this.maxCosine = Math.cos(radians);
|
||||
this.threshold = threshold * threshold;
|
||||
}
|
||||
|
||||
start(x: number, y: number) {
|
||||
this.startX = x;
|
||||
this.startY = y;
|
||||
this.isPan = 0;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
detect(x: number, y: number): boolean {
|
||||
if (!this.dirty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const deltaX = (x - this.startX);
|
||||
const deltaY = (y - this.startY);
|
||||
const distance = deltaX * deltaX + deltaY * deltaY;
|
||||
|
||||
if (distance < this.threshold) {
|
||||
return false;
|
||||
}
|
||||
const hypotenuse = Math.sqrt(distance);
|
||||
const cosine = (this.isDirX ? deltaX : deltaY) / hypotenuse;
|
||||
|
||||
if (cosine > this.maxCosine) {
|
||||
this.isPan = 1;
|
||||
|
||||
} else if (cosine < -this.maxCosine) {
|
||||
this.isPan = -1;
|
||||
|
||||
} else {
|
||||
this.isPan = 0;
|
||||
}
|
||||
|
||||
this.dirty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
isGesture(): boolean {
|
||||
return this.isPan !== 0;
|
||||
}
|
||||
|
||||
getDirection(): number {
|
||||
return this.isPan;
|
||||
}
|
||||
export interface PanRecognizer {
|
||||
start(x: number, y: number): void;
|
||||
detect(x: number, y: number): boolean;
|
||||
isGesture(): boolean;
|
||||
getDirection(): number;
|
||||
}
|
||||
|
||||
export function createPanRecognizer(direction: string, thresh: number, maxAngle: number): PanRecognizer {
|
||||
const radians = maxAngle * (Math.PI / 180);
|
||||
const isDirX = direction === 'x';
|
||||
const maxCosine = Math.cos(radians);
|
||||
const threshold = thresh * thresh;
|
||||
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
|
||||
let dirty = false;
|
||||
let isPan = 0;
|
||||
|
||||
return {
|
||||
start(x: number, y: number) {
|
||||
startX = x;
|
||||
startY = y;
|
||||
isPan = 0;
|
||||
dirty = true;
|
||||
},
|
||||
|
||||
detect(x: number, y: number): boolean {
|
||||
if (!dirty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const deltaX = (x - startX);
|
||||
const deltaY = (y - startY);
|
||||
const distance = deltaX * deltaX + deltaY * deltaY;
|
||||
|
||||
if (distance < threshold) {
|
||||
return false;
|
||||
}
|
||||
const hypotenuse = Math.sqrt(distance);
|
||||
const cosine = (isDirX ? deltaX : deltaY) / hypotenuse;
|
||||
|
||||
if (cosine > maxCosine) {
|
||||
isPan = 1;
|
||||
|
||||
} else if (cosine < -maxCosine) {
|
||||
isPan = -1;
|
||||
|
||||
} else {
|
||||
isPan = 0;
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
return true;
|
||||
},
|
||||
|
||||
isGesture(): boolean {
|
||||
return isPan !== 0;
|
||||
},
|
||||
|
||||
getDirection(): number {
|
||||
return isPan;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user