@antv/g-plugin-gesture
Version:
A G plugin for Gesture implemented with PointerEvents
516 lines (490 loc) • 16.9 kB
JavaScript
/*!
* @antv/g-plugin-gesture
* @description A G plugin for Gesture implemented with PointerEvents
* @version 2.0.31
* @date 1/17/2025, 2:07:02 PM
* @author AntVis
* @docs https://g.antv.antgroup.com/
*/
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
import _createClass from '@babel/runtime/helpers/createClass';
import _callSuper from '@babel/runtime/helpers/callSuper';
import _inherits from '@babel/runtime/helpers/inherits';
import { AbstractRendererPlugin } from '@antv/g-lite';
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
/**
* TODO: use clock from g later.
*/
var clock = typeof performance === 'object' && performance.now ? performance : Date;
// 计算滑动的方向
var calcDirection = function calcDirection(start, end) {
var xDistance = end.x - start.x;
var 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点之间的距离
var calcDistance = function calcDistance(point1, point2) {
var xDistance = Math.abs(point2.x - point1.x);
var yDistance = Math.abs(point2.y - point1.y);
return Math.sqrt(xDistance * xDistance + yDistance * yDistance);
};
var getCenter = function getCenter(point1, point2) {
var x = point1.x + (point2.x - point1.x) / 2;
var y = point1.y + (point2.y - point1.y) / 2;
return {
x: x,
y: y
};
};
var PRESS_DELAY = 250;
var GesturePlugin = /*#__PURE__*/function () {
function GesturePlugin(options) {
var _this = this;
_classCallCheck(this, GesturePlugin);
this.evCache = [];
this.startPoints = [];
// 用来记录当前触发的事件
this.processEvent = {};
this.throttleTimer = 0;
this.emitThrottles = [];
this._start = function (ev, target) {
// 每次触点开始都重置事件
_this.reset();
// 记录touch start 的时间
_this.startTime = clock.now();
var evCache = _this.evCache,
startPoints = _this.startPoints;
if (ev) {
var _ev$nativeEvent;
var pointerId = ev.pointerId,
x = ev.x,
y = ev.y;
// evcache 已经存在的 pointerId, 做替换
var existIdx = evCache.findIndex(function (item) {
return pointerId === item.pointerId;
});
if (existIdx !== -1) {
evCache.splice(existIdx, 1);
}
// evCache 不存在的 pointerId, 添加
evCache.push({
pointerId: pointerId,
x: x,
y: y,
ev: ev
});
// @ts-ignore 对齐touches evCache 存在,touches 不存在,移除
var evTouches = _toConsumableArray(((_ev$nativeEvent = ev.nativeEvent) === null || _ev$nativeEvent === void 0 ? void 0 : _ev$nativeEvent.touches) || []);
var _loop = function _loop(i) {
var isInTouches = evTouches.find(function (touch) {
return evCache[i].pointerId === touch.identifier;
});
// 在touches中存在
if (isInTouches) {
return 1; // continue
}
// 在touches中不存在
evCache.splice(i, 1);
};
for (var i = evCache.length - 1; i > -1; i--) {
if (_loop(i)) continue;
}
}
// 重置 startPoints
startPoints.length = evCache.length;
for (var _i = 0; _i < evCache.length; _i++) {
var _evCache$_i = evCache[_i],
_x = _evCache$_i.x,
_y = _evCache$_i.y;
var point = {
x: _x,
y: _y
};
startPoints[_i] = point;
}
// 单指事件
if (startPoints.length === 1) {
var event = evCache[0].ev;
// 如果touchstart后停顿250ms, 则也触发press事件
// @ts-ignore
_this.pressTimeout = setTimeout(function () {
// 这里固定触发press事件
var eventType = 'press';
var direction = 'none';
event.direction = direction;
event.deltaX = 0;
event.deltaY = 0;
event.points = startPoints;
_this.emitStart(eventType, event, target);
event.type = eventType;
target.dispatchEvent(event);
_this.eventType = eventType;
_this.direction = direction;
_this.movingTarget = target;
}, PRESS_DELAY);
return;
}
// 目前只处理双指
_this.startDistance = calcDistance(startPoints[0], startPoints[1]);
_this.center = getCenter(startPoints[0], startPoints[1]);
};
this._move = function (ev, target) {
_this.clearPressTimeout();
var startPoints = _this.startPoints,
evCache = _this.evCache;
if (!startPoints.length) return;
var x = ev.x,
y = ev.y,
pointerId = ev.pointerId;
// Find this event in the cache and update its record with this event
for (var i = 0, len = evCache.length; i < len; i++) {
if (pointerId === evCache[i].pointerId) {
evCache[i] = {
pointerId: pointerId,
x: x,
y: y,
ev: ev
};
break;
}
}
var point = {
x: x,
y: y
};
var points = evCache.map(function (ev) {
return {
x: ev.x,
y: ev.y
};
});
// 记录最后2次move的时间和坐标,为了给swipe事件用
var now = clock.now();
_this.prevMoveTime = _this.lastMoveTime;
_this.prevMovePoint = _this.lastMovePoint;
_this.lastMoveTime = now;
_this.lastMovePoint = point;
if (startPoints.length === 1) {
var startPoint = startPoints[0];
var deltaX = x - startPoint.x;
var deltaY = y - startPoint.y;
var direction = _this.direction || calcDirection(startPoint, point);
_this.direction = direction;
// 获取press或者pan的事件类型
// press 按住滑动, pan表示平移
// 如果start后立刻move,则触发pan, 如果有停顿,则触发press
var eventType = _this.getEventType(point, target, ev);
ev.direction = direction;
ev.deltaX = deltaX;
ev.deltaY = deltaY;
ev.points = points;
_this.emitStart(eventType, ev, target);
ev.type = eventType;
_this.refreshAndGetTarget(target).dispatchEvent(ev);
return;
}
// 多指触控
var startDistance = _this.startDistance;
var currentDistance = calcDistance(points[0], points[1]);
// 缩放比例
ev.zoom = currentDistance / startDistance;
ev.center = _this.center;
ev.points = points;
// 触发缩放事件
_this.emitStart('pinch', ev, target);
// touch 多指会被拆成多个手指的 move, 会触发多次 move,所以这里需要做节流
_this._throttleEmit('pinch', ev, target);
};
this._end = function (ev, target) {
var evCache = _this.evCache,
startPoints = _this.startPoints;
var points = evCache.map(function (ev) {
return {
x: ev.x,
y: ev.y
};
});
ev.points = points;
_this.emitEnd(ev, _this.refreshAndGetTarget(target));
// 单指
if (evCache.length === 1) {
// swipe事件处理, 在end之后触发
var now = clock.now();
var lastMoveTime = _this.lastMoveTime;
// 做这个判断是为了最后一次touchmove后到end前,是否还有一个停顿的过程
// 100 是拍的一个值,理论这个值会很短,一般不卡顿的话在10ms以内
if (now - lastMoveTime < 100) {
var prevMoveTime = _this.prevMoveTime || _this.startTime;
var intervalTime = lastMoveTime - prevMoveTime;
// 时间间隔一定要大于0, 否则计算没意义
if (intervalTime > 0) {
var prevMovePoint = _this.prevMovePoint || startPoints[0];
var lastMovePoint = _this.lastMovePoint || startPoints[0];
// move速率
var velocity = calcDistance(prevMovePoint, lastMovePoint) / intervalTime;
// 0.3 是参考hammerjs的设置
if (velocity > 0.3) {
ev.velocity = velocity;
ev.direction = calcDirection(prevMovePoint, lastMovePoint);
ev.type = 'swipe';
target.dispatchEvent(ev);
}
}
}
}
// remove event from cache
for (var i = 0, len = evCache.length; i < len; i++) {
if (evCache[i].pointerId === ev.pointerId) {
evCache.splice(i, 1);
startPoints.splice(i, 1);
break;
}
}
_this.reset();
// 多指离开 1 指后,重新触发一次start
if (evCache.length > 0) {
_this._start(undefined, target);
}
};
this._cancel = function (ev, target) {
var evCache = _this.evCache;
var points = evCache.map(function (ev) {
return {
x: ev.x,
y: ev.y
};
});
ev.points = points;
_this.emitEnd(ev, _this.refreshAndGetTarget(target));
_this.evCache = [];
_this.reset();
};
this.options = options;
}
return _createClass(GesturePlugin, [{
key: "apply",
value: function apply(context) {
var _this2 = this;
var renderingService = context.renderingService,
renderingContext = context.renderingContext;
var document = renderingContext.root.ownerDocument;
var canvas = document.defaultView;
this.canvas = canvas;
var getGestureEventTarget = function getGestureEventTarget(target) {
var isDocument = target === document;
return isDocument && _this2.options.isDocumentGestureEnabled ? document : target;
};
var handlePointermove = function handlePointermove(ev) {
var target = getGestureEventTarget(ev.target);
target && _this2._move(ev, target);
};
var handlePointerdown = function handlePointerdown(ev) {
var target = getGestureEventTarget(ev.target);
target && _this2._start(ev, target);
};
var handlePointerup = function handlePointerup(ev) {
var target = getGestureEventTarget(ev.target);
target && _this2._end(ev, target);
};
var handlePointercancel = function handlePointercancel(ev) {
var target = getGestureEventTarget(ev.target);
target && _this2._cancel(ev, target);
};
var handlePointercanceloutside = function handlePointercanceloutside(ev) {
var target = getGestureEventTarget(ev.target);
target && _this2._end(ev, target);
};
renderingService.hooks.init.tap(GesturePlugin.tag, function () {
canvas.addEventListener('pointermove', handlePointermove);
canvas.addEventListener('pointerdown', handlePointerdown);
canvas.addEventListener('pointerup', handlePointerup);
canvas.addEventListener('pointercancel', handlePointercancel);
canvas.addEventListener('pointerupoutside', handlePointercanceloutside);
});
renderingService.hooks.destroy.tap(GesturePlugin.tag, function () {
canvas.removeEventListener('pointermove', handlePointermove);
canvas.removeEventListener('pointerdown', handlePointerdown);
canvas.removeEventListener('pointerup', handlePointerup);
canvas.removeEventListener('pointercancel', handlePointercancel);
canvas.removeEventListener('pointerupoutside', handlePointercanceloutside);
});
}
}, {
key: "getEventType",
value: function getEventType(point, target, ev) {
var eventType = this.eventType,
startTime = this.startTime,
startPoints = this.startPoints;
if (eventType) {
return eventType;
}
// move的时候缓存节点,后续move和end都会使用这个target派发事件
this.movingTarget = target;
// 冒泡路径中是否有pan事件
this.isPanListenerInPath = ev.path.some(function (ele) {
var _ele$emitter;
return !!((_ele$emitter = ele.emitter) !== null && _ele$emitter !== void 0 && (_ele$emitter = _ele$emitter.eventNames()) !== null && _ele$emitter !== void 0 && _ele$emitter.includes('pan'));
});
var type;
// 如果没有pan事件的监听,默认都是press
if (!this.isPanListenerInPath) {
type = 'press';
} else {
// 如果有pan事件的处理,press则需要停顿250ms, 且移动距离小于10
var now = clock.now();
if (now - startTime > PRESS_DELAY && calcDistance(startPoints[0], point) < 10) {
type = 'press';
} else {
type = 'pan';
}
}
this.eventType = type;
return type;
}
}, {
key: "enable",
value: function enable(eventType) {
this.processEvent[eventType] = true;
}
// 是否进行中的事件
}, {
key: "isProcess",
value: function isProcess(eventType) {
return this.processEvent[eventType];
}
// 触发start事件
}, {
key: "emitStart",
value: function emitStart(type, ev, target) {
if (this.isProcess(type)) {
return;
}
this.enable(type);
ev.type = "".concat(type, "start");
target.dispatchEvent(ev);
}
// 触发事件
}, {
key: "_throttleEmit",
value: function _throttleEmit(type, ev, target) {
var _this3 = this;
// 主要是节流处理
this.pushEvent(type, ev);
var throttleTimer = this.throttleTimer,
emitThrottles = this.emitThrottles,
processEvent = this.processEvent;
if (throttleTimer) {
return;
}
this.throttleTimer = this.canvas.requestAnimationFrame(function () {
for (var i = 0, len = emitThrottles.length; i < len; i++) {
var _emitThrottles$i = emitThrottles[i],
_type = _emitThrottles$i.type,
_ev = _emitThrottles$i.ev;
if (processEvent[_type]) {
_ev.type = _type;
target.dispatchEvent(_ev);
}
}
// 清空
_this3.throttleTimer = 0;
_this3.emitThrottles.length = 0;
});
}
// 触发end事件
}, {
key: "emitEnd",
value: function emitEnd(ev, target) {
var processEvent = this.processEvent;
Object.keys(processEvent).forEach(function (type) {
ev.type = "".concat(type, "end");
target.dispatchEvent(ev);
delete processEvent[type];
});
}
}, {
key: "pushEvent",
value: function pushEvent(type, ev) {
var emitThrottles = this.emitThrottles;
var newEvent = {
type: type,
ev: ev
};
for (var i = 0, len = emitThrottles.length; i < len; i++) {
if (emitThrottles[i].type === type) {
emitThrottles.splice(i, 1, newEvent);
return;
}
}
emitThrottles.push(newEvent);
}
}, {
key: "clearPressTimeout",
value: function clearPressTimeout() {
if (this.pressTimeout) {
clearTimeout(this.pressTimeout);
this.pressTimeout = null;
}
}
}, {
key: "refreshAndGetTarget",
value: function refreshAndGetTarget(target) {
if (this.movingTarget) {
// @ts-ignore
if (this.movingTarget && !this.movingTarget.isConnected) {
this.movingTarget = target;
}
return this.movingTarget;
}
return target;
}
}, {
key: "reset",
value: function reset() {
this.clearPressTimeout();
this.startTime = 0;
this.startDistance = 0;
this.direction = null;
this.eventType = null;
this.prevMoveTime = 0;
this.prevMovePoint = null;
this.lastMoveTime = 0;
this.lastMovePoint = null;
this.movingTarget = null;
this.isPanListenerInPath = null;
}
}]);
}();
GesturePlugin.tag = 'Gesture';
var Plugin = /*#__PURE__*/function (_AbstractRendererPlug) {
function Plugin() {
var _this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, Plugin);
_this = _callSuper(this, Plugin);
_this.name = 'gesture';
_this.options = options;
return _this;
}
_inherits(Plugin, _AbstractRendererPlug);
return _createClass(Plugin, [{
key: "init",
value: function init() {
this.addRenderingPlugin(new GesturePlugin(_objectSpread({
isDocumentGestureEnabled: false
}, this.options)));
}
}, {
key: "destroy",
value: function destroy() {
this.removeAllRenderingPlugins();
}
}]);
}(AbstractRendererPlugin);
export { Plugin };
//# sourceMappingURL=index.esm.js.map