diff --git a/core/src/components/animation-controller/animator.tsx b/core/src/components/animation-controller/animator.tsx index 1ce7462bfb8..5a9169d8bca 100644 --- a/core/src/components/animation-controller/animator.tsx +++ b/core/src/components/animation-controller/animator.tsx @@ -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'; } } } diff --git a/core/src/components/menu/menu.scss b/core/src/components/menu/menu.scss index 75bb99bacb9..f5aa07aaa7e 100644 --- a/core/src/components/menu/menu.scss +++ b/core/src/components/menu/menu.scss @@ -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; diff --git a/core/src/utils/gesture/gesture.ts b/core/src/utils/gesture/gesture.ts index 5a308617dce..3eeebc06160 100644 --- a/core/src/utils/gesture/gesture.ts +++ b/core/src/utils/gesture/gesture.ts @@ -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; - 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; + onStart?: GestureCallback; + onMove?: GestureCallback; + onEnd?: GestureCallback; + notCaptured?: GestureCallback; +} diff --git a/core/src/utils/gesture/pointer-events.ts b/core/src/utils/gesture/pointer-events.ts index a408cf4c37b..dc8afacfa4d 100644 --- a/core/src/utils/gesture/pointer-events.ts +++ b/core/src/utils/gesture/pointer-events.ts @@ -2,141 +2,134 @@ import { addEventListener } from './listener'; const MOUSE_WAIT = 2000; -export class PointerEvents { - - private rmTouchStart?: () => void; - private rmTouchMove?: () => void; - private rmTouchEnd?: () => void; - private rmTouchCancel?: () => void; - - 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)) { +export function createPointerEvents( + el: Node, + pointerDown: any, + pointerMove: any, + pointerUp: any, + options: EventListenerOptions +) { + + 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; + + 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; diff --git a/core/src/utils/gesture/recognizers.ts b/core/src/utils/gesture/recognizers.ts index e010fec0543..bb44618b81a 100644 --- a/core/src/utils/gesture/recognizers.ts +++ b/core/src/utils/gesture/recognizers.ts @@ -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; - } +export interface PanRecognizer { + start(x: number, y: number): void; + detect(x: number, y: number): boolean; + isGesture(): boolean; + getDirection(): number; +} - const deltaX = (x - this.startX); - const deltaY = (y - this.startY); - const distance = deltaX * deltaX + deltaY * deltaY; +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; - if (distance < this.threshold) { - return false; - } - const hypotenuse = Math.sqrt(distance); - const cosine = (this.isDirX ? deltaX : deltaY) / hypotenuse; + let startX = 0; + let startY = 0; - if (cosine > this.maxCosine) { - this.isPan = 1; + let dirty = false; + let isPan = 0; - } else if (cosine < -this.maxCosine) { - this.isPan = -1; + return { + start(x: number, y: number) { + startX = x; + startY = y; + isPan = 0; + dirty = true; + }, - } else { - this.isPan = 0; - } + detect(x: number, y: number): boolean { + if (!dirty) { + return false; + } + + const deltaX = (x - startX); + const deltaY = (y - startY); + const distance = deltaX * deltaX + deltaY * deltaY; - this.dirty = false; - return true; - } + if (distance < threshold) { + return false; + } + const hypotenuse = Math.sqrt(distance); + const cosine = (isDirX ? deltaX : deltaY) / hypotenuse; - isGesture(): boolean { - return this.isPan !== 0; - } + if (cosine > maxCosine) { + isPan = 1; - getDirection(): number { - return this.isPan; - } + } else if (cosine < -maxCosine) { + isPan = -1; + + } else { + isPan = 0; + } + + dirty = false; + return true; + }, + + isGesture(): boolean { + return isPan !== 0; + }, + + getDirection(): number { + return isPan; + } + }; }