@antv/g2
Version:
the Grammar of Graphics in Javascript
294 lines (258 loc) • 8.45 kB
text/typescript
import { ICanvas } from '../../dependents';
import { Point } from '../../interface';
import { View } from '../view';
import { Controller } from './base';
const PRESS_DELAY = 250;
// 计算滑动的方向
const calcDirection = (start: Point, end: Point) => {
const xDistance = end.x - start.x;
const yDistance = end.y - start.y;
// x 的距离大于y 说明是横向,否则就是纵向
if (Math.abs(xDistance) > Math.abs(yDistance)) {
return xDistance > 0 ? 'right' : 'left';
}
return yDistance > 0 ? 'down' : 'up';
};
// 计算2点之间的距离
const calcDistance = (point1: Point, point2: Point) => {
const xDistance = Math.abs(point2.x - point1.x);
const yDistance = Math.abs(point2.y - point1.y);
return Math.sqrt(xDistance * xDistance + yDistance * yDistance);
};
const getCenter = (point1, point2) => {
const x = (point1.x + point2.x) / 2;
const y = (point1.y + point2.y) / 2;
return { x, y };
};
const convertPoints = (touches, canvas: ICanvas) => {
if (!touches) {
return;
}
const points = [];
const len = touches.length;
for (let i = 0; i < len; i++) {
const touch = touches[i];
// x, y: 相对canvas原点的位置,clientX, clientY 相对于可视窗口的位置
const { clientX, clientY } = touch;
const point = canvas.getPointByClient(clientX, clientY);
points.push(point);
}
return points;
};
export default class GestureController extends Controller {
private canvas: ICanvas;
private processEvent = {};
private startTime: number;
private startPoints: Point[];
private startDistance: number;
private center: Point;
private pressTimeout;
private direction: string;
private prevMoveTime: number;
private prevMovePoints: Point[];
private lastMoveTime: number;
private lastMovePoints: Point[];
private eventType: string;
constructor(view: View) {
super(view);
this.canvas = view.getCanvas();
this.delegateEvent();
// 用来记录当前触发的事件
this.processEvent = {};
}
public get name(): string {
return 'gesture';
}
public init() {}
public render() {}
public layout() {}
public update() {}
public destroy() {
this.reset();
this.offEvent();
this.processEvent = null;
}
private delegateEvent() {
// 代理这几个事件
this.canvas.on('touchstart', this.touchStart);
this.canvas.on('touchmove', this.touchMove);
this.canvas.on('touchend', this.touchEnd);
}
private offEvent() {
this.canvas.off('touchstart', this.touchStart);
this.canvas.off('touchmove', this.touchMove);
this.canvas.off('touchend', this.touchEnd);
}
private touchStart = (ev): void => {
const points = convertPoints(ev.originalEvent.touches, this.canvas);
if (!points) {
return;
}
ev.points = points;
// 防止上次的内容没有清理掉,重新reset下
this.reset();
// 记录touch start 的时间
this.startTime = Date.now();
// 记录touch start 的点
this.startPoints = points;
if (points.length > 1) {
this.startDistance = calcDistance(points[0], points[1]);
this.center = getCenter(points[0], points[1]);
} else {
// 如果touchstart后停顿250ms, 则也触发press事件
this.pressTimeout = setTimeout(() => {
// 这里固定触发press事件
const eventType = 'press';
ev.direction = 'none';
this.emitStart(eventType, ev);
this.emitEvent(eventType, ev);
this.eventType = eventType;
}, PRESS_DELAY);
}
};
private touchMove = (ev): void => {
const points = convertPoints(ev.originalEvent.touches, this.canvas);
if (!points) {
return;
}
this.clearPressTimeout();
ev.points = points;
const startPoints = this.startPoints;
if (!startPoints) {
return;
}
// 多指触控
if (points.length > 1) {
// touchstart的距离
const startDistance = this.startDistance;
const currentDistance = calcDistance(points[0], points[1]);
ev.zoom = currentDistance / startDistance;
ev.center = this.center;
// 触发缩放事件
this.emitStart('pinch', ev);
this.emitEvent('pinch', ev);
} else {
const deltaX = points[0].x - startPoints[0].x;
const deltaY = points[0].y - startPoints[0].y;
const direction = this.direction || calcDirection(startPoints[0], points[0]);
this.direction = direction;
// 获取press或者pan的事件类型
// press 按住滑动, pan表示平移
// 如果start后立刻move,则触发pan, 如果有停顿,则触发press
const eventType = this.getEventType(points);
ev.direction = direction;
ev.deltaX = deltaX;
ev.deltaY = deltaY;
this.emitStart(eventType, ev);
this.emitEvent(eventType, ev);
// 记录最后2次move的时间和坐标,为了给swipe事件用
const prevMoveTime = this.lastMoveTime;
const now = Date.now();
// 最后2次的时间间隔一定要大于0,否则swipe没发计算
if (now - prevMoveTime > 0) {
this.prevMoveTime = prevMoveTime;
this.prevMovePoints = this.lastMovePoints;
this.lastMoveTime = now;
this.lastMovePoints = points;
}
}
};
private touchEnd = (ev): void => {
this.emitEnd(ev);
// swipe事件处理, 在touchend之后触发
const lastMoveTime = this.lastMoveTime;
const now = Date.now();
// 做这个判断是为了最后一次touchmove后到end前,还有一个停顿的过程
// 100 是拍的一个值,理论这个值会很短,一般不卡顿的话在10ms以内
if (now - lastMoveTime < 100) {
const prevMoveTime = this.prevMoveTime || this.startTime;
const intervalTime = lastMoveTime - prevMoveTime;
// 时间间隔一定要大于0, 否则计算没意义
if (intervalTime > 0) {
const prevMovePoints = this.prevMovePoints || this.startPoints;
const lastMovePoints = this.lastMovePoints;
// move速率
const velocity = calcDistance(prevMovePoints[0], lastMovePoints[0]) / intervalTime;
// 0.3 是参考hammerjs的设置
if (velocity > 0.3) {
ev.velocity = velocity;
ev.direction = calcDirection(prevMovePoints[0], lastMovePoints[0]);
this.emitEvent('swipe', ev);
}
}
}
this.reset();
const touches = ev.touches;
// 当多指只释放了1指时也会触发end, 这时重新触发一次start
if (touches && touches.length > 0) {
this.touchStart(ev);
}
};
private emitEvent(type, ev) {
const view = this.view;
view.emit(type, ev);
}
// 触发start事件
private emitStart(type, ev) {
if (this.isProcess(type)) {
return;
}
this.enable(type);
this.emitEvent(`${type}start`, ev);
}
// 触发end事件
private emitEnd(ev) {
const processEvent = this.processEvent;
Object.keys(processEvent).forEach((type) => {
this.emitEvent(`${type}end`, ev);
delete processEvent[type];
});
}
private enable(eventType: string) {
this.processEvent[eventType] = true;
}
// 是否进行中的事件
private isProcess(eventType: string) {
return this.processEvent[eventType];
}
private reset() {
this.clearPressTimeout();
this.startTime = 0;
this.startPoints = null;
this.startDistance = 0;
this.direction = null;
this.eventType = null;
this.prevMoveTime = 0;
this.prevMovePoints = null;
this.lastMoveTime = 0;
this.lastMovePoints = null;
}
private clearPressTimeout() {
if (this.pressTimeout) {
clearTimeout(this.pressTimeout);
this.pressTimeout = 0;
}
}
private getEventType(points) {
const { eventType, view, startTime, startPoints } = this;
if (eventType) {
return eventType;
}
let type;
const panEventListeners = view.getEvents().pan;
// 如果 view 上没有 pan 事件的监听,默认都是 press
if (!panEventListeners || !panEventListeners.length) {
type = 'press';
} else {
// 如果有pan事件的处理,press则需要停顿250ms, 且移动距离小于10
const now = Date.now();
if (now - startTime > PRESS_DELAY && calcDistance(startPoints[0], points[0]) < 10) {
type = 'press';
} else {
type = 'pan';
}
}
this.eventType = type;
return type;
}
}