mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 05:58:26 +08:00
refactor(gestures): no longer use hammer for drag gestures
This commit is contained in:

committed by
Adam Bradley

parent
11a24b98aa
commit
5909fa4ba5
@ -1,42 +1,142 @@
|
||||
import { Gesture } from './gesture';
|
||||
|
||||
import { defaults } from '../util';
|
||||
import { GestureDelegate } from '../gestures/gesture-controller';
|
||||
import { PointerEvents, UIEventManager } from '../util/ui-event-manager';
|
||||
import { PanRecognizer } from './recognizers';
|
||||
import { pointerCoord, Coordinates } from '../util/dom';
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export interface PanGestureConfig {
|
||||
threshold?: number;
|
||||
maxAngle?: number;
|
||||
direction?: 'x' | 'y';
|
||||
gesture?: GestureDelegate;
|
||||
}
|
||||
|
||||
export class DragGesture extends Gesture {
|
||||
public dragging: boolean;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class PanGesture {
|
||||
private dragging: boolean;
|
||||
private events: UIEventManager = new UIEventManager(false);
|
||||
private pointerEvents: PointerEvents;
|
||||
private detector: PanRecognizer;
|
||||
private started: boolean = false;
|
||||
private captured: boolean = false;
|
||||
public isListening: boolean = false;
|
||||
protected gestute: GestureDelegate;
|
||||
protected direction: string;
|
||||
|
||||
constructor(element: HTMLElement, opts = {}) {
|
||||
defaults(opts, {});
|
||||
super(element, opts);
|
||||
constructor(private element: HTMLElement, opts: PanGestureConfig = {}) {
|
||||
defaults(opts, {
|
||||
threshold: 20,
|
||||
maxAngle: 40,
|
||||
direction: 'x'
|
||||
});
|
||||
this.gestute = opts.gesture;
|
||||
this.direction = opts.direction;
|
||||
this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle);
|
||||
}
|
||||
|
||||
listen() {
|
||||
super.listen();
|
||||
|
||||
this.on('panstart', (ev: UIEvent) => {
|
||||
if (this.onDragStart(ev) !== false) {
|
||||
this.dragging = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.on('panmove', (ev: UIEvent) => {
|
||||
if (!this.dragging) return;
|
||||
if (this.onDrag(ev) === false) {
|
||||
this.dragging = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.on('panend', (ev: UIEvent) => {
|
||||
if (!this.dragging) return;
|
||||
this.onDragEnd(ev);
|
||||
this.dragging = false;
|
||||
});
|
||||
if (!this.isListening) {
|
||||
this.pointerEvents = this.events.pointerEvents({
|
||||
element: this.element,
|
||||
pointerDown: this.pointerDown.bind(this),
|
||||
pointerMove: this.pointerMove.bind(this),
|
||||
pointerUp: this.pointerUp.bind(this),
|
||||
});
|
||||
this.isListening = true;
|
||||
}
|
||||
}
|
||||
|
||||
onDrag(ev: any): boolean { return true; }
|
||||
onDragStart(ev: any): boolean { return true; }
|
||||
onDragEnd(ev: any): void {}
|
||||
unlisten() {
|
||||
this.gestute && this.gestute.release();
|
||||
this.events.unlistenAll();
|
||||
this.isListening = false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.gestute && this.gestute.destroy();
|
||||
this.unlisten();
|
||||
this.element = null;
|
||||
}
|
||||
|
||||
pointerDown(ev: any): boolean {
|
||||
if (this.started) {
|
||||
return;
|
||||
}
|
||||
if (!this.canStart(ev)) {
|
||||
return false;
|
||||
}
|
||||
if (this.gestute) {
|
||||
// Release fallback
|
||||
this.gestute.release();
|
||||
// Start gesture
|
||||
if (!this.gestute.start()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let coord = pointerCoord(ev);
|
||||
this.detector.start(coord);
|
||||
this.started = true;
|
||||
this.captured = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pointerMove(ev: any) {
|
||||
if (!this.started) {
|
||||
return;
|
||||
}
|
||||
if (this.captured) {
|
||||
this.onDragMove(ev);
|
||||
return;
|
||||
}
|
||||
let coord = pointerCoord(ev);
|
||||
if (this.detector.detect(coord)) {
|
||||
|
||||
if (this.detector.pan() !== 0 && this.canCapture(ev) &&
|
||||
(!this.gestute || this.gestute.capture())) {
|
||||
this.onDragStart(ev);
|
||||
this.captured = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Detection/capturing was not successful, aborting!
|
||||
this.started = false;
|
||||
this.captured = false;
|
||||
this.pointerEvents.stop();
|
||||
this.notCaptured(ev);
|
||||
}
|
||||
}
|
||||
|
||||
pointerUp(ev: any) {
|
||||
if (!this.started) {
|
||||
return;
|
||||
}
|
||||
this.gestute && this.gestute.release();
|
||||
|
||||
if (this.captured) {
|
||||
this.onDragEnd(ev);
|
||||
} else {
|
||||
this.notCaptured(ev);
|
||||
}
|
||||
this.captured = false;
|
||||
this.started = false;
|
||||
}
|
||||
|
||||
getNativeElement(): HTMLElement {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
// Implemented in a subclass
|
||||
canStart(ev: any): boolean { return true; }
|
||||
canCapture(ev: any): boolean { return true; }
|
||||
onDragStart(ev: any) { }
|
||||
onDragMove(ev: any) { }
|
||||
onDragEnd(ev: any) { }
|
||||
notCaptured(ev: any) { }
|
||||
}
|
||||
|
@ -4,11 +4,16 @@ import { App } from '../components/app/app';
|
||||
|
||||
export const enum GesturePriority {
|
||||
Minimun = -10000,
|
||||
NavigationOptional = -20,
|
||||
Navigation = -10,
|
||||
VeryLow = -20,
|
||||
Low = -10,
|
||||
Normal = 0,
|
||||
Interactive = 10,
|
||||
Input = 20,
|
||||
High = 10,
|
||||
VeryHigh = 20,
|
||||
|
||||
SlidingItem = Low,
|
||||
MenuSwipe = High,
|
||||
GoBackSwipe = VeryHigh,
|
||||
Refresher = Normal,
|
||||
}
|
||||
|
||||
export const enum DisableScroll {
|
||||
|
58
src/gestures/recognizers.ts
Normal file
58
src/gestures/recognizers.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { pointerCoord, Coordinates } from '../util/dom';
|
||||
|
||||
export class PanRecognizer {
|
||||
private startCoord: Coordinates;
|
||||
private dirty: boolean = false;
|
||||
private threshold: number;
|
||||
private maxCosine: number;
|
||||
private _angle: any = 0;
|
||||
private _isPan: number = 0;
|
||||
|
||||
constructor(private direction: string, threshold: number, maxAngle: number) {
|
||||
let radians = maxAngle * (Math.PI / 180);
|
||||
this.maxCosine = Math.cos(radians);
|
||||
this.threshold = threshold * threshold;
|
||||
}
|
||||
|
||||
start(coord: Coordinates) {
|
||||
this.startCoord = coord;
|
||||
this._angle = 0;
|
||||
this._isPan = 0;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
detect(coord: Coordinates): boolean {
|
||||
if (!this.dirty) {
|
||||
return false;
|
||||
}
|
||||
let deltaX = (coord.x - this.startCoord.x);
|
||||
let deltaY = (coord.y - this.startCoord.y);
|
||||
let distance = deltaX * deltaX + deltaY * deltaY;
|
||||
if (distance >= this.threshold) {
|
||||
let angle = Math.atan2(deltaY, deltaX);
|
||||
let cosine = (this.direction === 'y')
|
||||
? Math.sin(angle)
|
||||
: Math.cos(angle);
|
||||
|
||||
this._angle = angle;
|
||||
if (cosine > this.maxCosine) {
|
||||
this._isPan = 1;
|
||||
} else if (cosine < -this.maxCosine) {
|
||||
this._isPan = -1;
|
||||
} else {
|
||||
this._isPan = 0;
|
||||
}
|
||||
this.dirty = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
angle(): any {
|
||||
return this._angle;
|
||||
}
|
||||
|
||||
pan(): number {
|
||||
return this._isPan;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import {SlideGesture} from './slide-gesture';
|
||||
import {defaults} from '../util/util';
|
||||
import {windowDimensions} from '../util/dom';
|
||||
import { SlideGesture } from './slide-gesture';
|
||||
import { defaults } from '../util/util';
|
||||
import { pointerCoord, windowDimensions } from '../util/dom';
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -22,8 +22,9 @@ export class SlideEdgeGesture extends SlideGesture {
|
||||
}
|
||||
|
||||
canStart(ev: any): boolean {
|
||||
let coord = pointerCoord(ev);
|
||||
this._d = this.getContainerDimensions();
|
||||
return this.edges.every(edge => this._checkEdge(edge, ev.center));
|
||||
return this.edges.every(edge => this._checkEdge(edge, coord));
|
||||
}
|
||||
|
||||
getContainerDimensions() {
|
||||
|
@ -1,16 +1,15 @@
|
||||
import {DragGesture} from './drag-gesture';
|
||||
import {clamp} from '../util';
|
||||
|
||||
import { PanGesture } from './drag-gesture';
|
||||
import { clamp } from '../util';
|
||||
import { pointerCoord } from '../util/dom';
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class SlideGesture extends DragGesture {
|
||||
export class SlideGesture extends PanGesture {
|
||||
public slide: SlideData = null;
|
||||
|
||||
constructor(element: HTMLElement, opts = {}) {
|
||||
super(element, opts);
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -20,7 +19,7 @@ export class SlideGesture extends DragGesture {
|
||||
getSlideBoundaries(slide: SlideData, ev: any) {
|
||||
return {
|
||||
min: 0,
|
||||
max: this.element.offsetWidth
|
||||
max: this.getNativeElement().offsetWidth
|
||||
};
|
||||
}
|
||||
|
||||
@ -33,48 +32,43 @@ export class SlideGesture extends DragGesture {
|
||||
return 0;
|
||||
}
|
||||
|
||||
canStart(ev: any): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
onDragStart(ev: any): boolean {
|
||||
if (!this.canStart(ev)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
onDragStart(ev: any) {
|
||||
this.slide = {};
|
||||
this.onSlideBeforeStart(this.slide, ev);
|
||||
|
||||
var {min, max} = this.getSlideBoundaries(this.slide, ev);
|
||||
let {min, max} = this.getSlideBoundaries(this.slide, ev);
|
||||
let coord = <any>pointerCoord(ev);
|
||||
this.slide.min = min;
|
||||
this.slide.max = max;
|
||||
this.slide.elementStartPos = this.getElementStartPos(this.slide, ev);
|
||||
this.slide.pointerStartPos = ev.center[this.direction];
|
||||
this.slide.pos = this.slide.pointerStartPos = coord[this.direction];
|
||||
this.slide.timestamp = Date.now();
|
||||
this.slide.started = true;
|
||||
this.slide.velocity = 0;
|
||||
this.onSlideStart(this.slide, ev);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onDrag(ev: any): boolean {
|
||||
if (!this.slide || !this.slide.started) {
|
||||
return false;
|
||||
}
|
||||
onDragMove(ev: any) {
|
||||
let coord = <any>pointerCoord(ev);
|
||||
let newPos = coord[this.direction];
|
||||
let newTimestamp = Date.now();
|
||||
let velocity = (newPos - this.slide.pos) / (newTimestamp - this.slide.timestamp);
|
||||
|
||||
this.slide.pos = ev.center[this.direction];
|
||||
this.slide.pos = newPos;
|
||||
this.slide.timestamp = newTimestamp;
|
||||
this.slide.distance = clamp(
|
||||
this.slide.min,
|
||||
this.slide.pos - this.slide.pointerStartPos + this.slide.elementStartPos,
|
||||
newPos - this.slide.pointerStartPos + this.slide.elementStartPos,
|
||||
this.slide.max
|
||||
);
|
||||
this.slide.delta = this.slide.pos - this.slide.pointerStartPos;
|
||||
this.slide.velocity = velocity;
|
||||
this.slide.delta = newPos - this.slide.pointerStartPos;
|
||||
this.onSlide(this.slide, ev);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onDragEnd(ev: any) {
|
||||
if (!this.slide || !this.slide.started) return;
|
||||
this.onSlideEnd(this.slide, ev);
|
||||
this.slide = null;
|
||||
}
|
||||
@ -85,6 +79,9 @@ export class SlideGesture extends DragGesture {
|
||||
onSlideEnd(slide?: SlideData, ev?: any): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export interface SlideData {
|
||||
min?: number;
|
||||
max?: number;
|
||||
@ -92,6 +89,8 @@ export interface SlideData {
|
||||
delta?: number;
|
||||
started?: boolean;
|
||||
pos?: any;
|
||||
timestamp?: number;
|
||||
pointerStartPos?: number;
|
||||
elementStartPos?: number;
|
||||
velocity?: number;
|
||||
}
|
||||
|
Reference in New Issue
Block a user