源码分析之Leaflet中dom模块DomEvent.Pointer的实现原理

概述

DomEvent.PointerLeaflet 框架中用于 ​ 统一处理指针事件(Pointer Events)​ 的核心模块,目的是将不同浏览器(尤其是旧版 IE)的指针事件(如 MSPointer)和标准指针事件(PointerEvent)抽象为统一的触摸事件(touchstart/touchmove 等),以便在不同浏览器中实现一致的触摸行为。

源码分析

源码实现如下

DomEvent.Pointer源码实现如下:

var POINTER_DOWN = Browser.msPointer ? "MSPointerDown" : "pointerdown";
var POINTER_MOVE = Browser.msPointer ? "MSPointerMove" : "pointermove";
var POINTER_UP = Browser.msPointer ? "MSPointerUp" : "pointerup";
var POINTER_CANCEL = Browser.msPointer ? "MSPointerCancel" : "pointercancel";
var pEvent = {
  touchstart: POINTER_DOWN,
  touchmove: POINTER_MOVE,
  touchend: POINTER_UP,
  touchcancel: POINTER_CANCEL,
};
var handle = {
  touchstart: _onPointerStart,
  touchmove: _handlePointer,
  touchend: _handlePointer,
  touchcancel: _handlePointer,
};
var _pointers = {};
var _pointerDocListener = false;

export function addPointerListener(obj, type, handler) {
  if (type === "touchstart") {
    _addPointerDocListener();
  }
  if (!handle[type]) {
    console.warn("wrong event specified:", type);
    return falseFn;
  }
  handler = handle[type].bind(this, handler);
  obj.addEventListener(pEvent[type], handler, false);
  return handler;
}

export function removePointerListener(obj, type, handler) {
  if (!pEvent[type]) {
    console.warn("wrong event specified:", type);
    return;
  }
  obj.removeEventListener(pEvent[type], handler, false);
}

function _globalPointerDown(e) {
  _pointers[e.pointerId] = e;
}

function _globalPointerMove(e) {
  if (_pointers[e.pointerId]) {
    _pointers[e.pointerId] = e;
  }
}

function _globalPointerUp(e) {
  delete _pointers[e.pointerId];
}

function _addPointerDocListener() {
  if (!_pointerDocListener) {
    document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
    document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
    document.addEventListener(POINTER_UP, _globalPointerUp, true);
    document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);

    _pointerDocListener = true;
  }
}

function _handlePointer(handler, e) {
  if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || "mouse")) {
    return;
  }

  e.touches = [];
  for (var i in _pointers) {
    e.touches.push(_pointers[i]);
  }
  e.changedTouches = [e];

  handler(e);
}

function _onPointerStart(handler, e) {
  if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
    DomEvent.preventDefault(e);
  }
  _handlePointer(handler, e);
}

源码详解

  1. 变量定义与兼容性处理
var POINTER_DOWN = Browser.msPointer ? "MSPointerDown" : "pointerdown";
var POINTER_MOVE = Browser.msPointer ? "MSPointerMove" : "pointermove";
var POINTER_UP = Browser.msPointer ? "MSPointerUp" : "pointerup";
var POINTER_CANCEL = Browser.msPointer ? "MSPointerCancel" : "pointercancel";
  • ​ 目的:根据浏览器是否支持 MSPointer(旧版 IE)选择对应的事件名称,实现兼容性适配。
    ​- 关键点Browser.msPointer 是 Leaflet 内部的浏览器特性检测标志,用于判断是否为旧版 IE。
  1. 事件类型映射
var pEvent = {
  touchstart: POINTER_DOWN,
  touchmove: POINTER_MOVE,
  touchend: POINTER_UP,
  touchcancel: POINTER_CANCEL,
};
var handle = {
  touchstart: _onPointerStart,
  touchmove: _handlePointer,
  touchend: _handlePointer,
  touchcancel: _handlePointer,
};
  • ​ 目的:将标准触摸事件类型(如 touchstart)映射到实际的指针事件(如 pointerdown)和对应的处理函数。

  • 关键点

    • pEvent:事件名映射表。
    • handle:事件处理函数映射表,统一入口处理不同事件。
  1. 指针状态管理
var _pointers = {}; // 存储当前活动的指针(如多个触控点)
var _pointerDocListener = false; // 标记是否已全局监听文档级指针事件

  • 目的:跟踪所有活动的指针(例如多点触控场景中的每个手指),确保事件处理的连续性。

​ 4. 添加/移除事件监听器

export function addPointerListener(obj, type, handler) {
  if (type === "touchstart") {
    _addPointerDocListener();
  }
  handler = handle[type].bind(this, handler);
  obj.addEventListener(pEvent[type], handler, false);
  return handler;
}

export function removePointerListener(obj, type, handler) {
  obj.removeEventListener(pEvent[type], handler, false);
}
  • 逻辑流程

    1. 当添加 touchstart 监听时,触发 _addPointerDocListener(),全局监听文档级指针事件。
    2. 将用户传入的 handler 绑定到对应的事件处理函数(如 _onPointerStart)。
    3. 使用 addEventListener 注册事件。
  • 关键点

    • bind(this, handler) 确保处理函数中的 this 指向正确。
    • 返回包装后的 handler,便于后续移除监听。
  1. 全局指针事件监听
function _addPointerDocListener() {
  if (!_pointerDocListener) {
    document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
    document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
    document.addEventListener(POINTER_UP, _globalPointerUp, true);
    document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
    _pointerDocListener = true;
  }
}
  • ​ 目的:在文档级别捕获所有指针事件,确保即使指针移动到元素外部,仍能跟踪状态。
  • 关键点
    • 使用 捕获阶段(true) 监听事件,确保优先处理。
    • _globalPointerDown/_globalPointerMove/_globalPointerUp 维护 _pointers 对象。
  1. 指针状态更新
function _globalPointerDown(e) {
  _pointers[e.pointerId] = e; // 记录指针按下时的状态
}
function _globalPointerMove(e) {
  if (_pointers[e.pointerId]) {
    _pointers[e.pointerId] = e; // 更新指针移动后的状态
  }
}
function _globalPointerUp(e) {
  delete _pointers[e.pointerId]; // 删除已释放的指针
}
  • ​ 目的:维护 _pointers 对象,记录所有活动指针的实时数据。
  1. 事件处理与转换
function _handlePointer(handler, e) {
  if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || "mouse")) {
    return; // 忽略鼠标事件
  }
  // 将指针事件转换为触摸事件结构
  e.touches = [];
  for (var i in _pointers) {
    e.touches.push(_pointers[i]);
  }
  e.changedTouches = [e]; // 当前触发事件的指针
  handler(e); // 调用用户的事件处理函数
}
  • ​ 关键点
    • 过滤鼠标事件:仅处理触控笔或手指触控。
    • ​ 模拟触摸事件结构
      • e.touches: 所有当前活动的触控点。
      • e.changedTouches: 当前发生变化的触控点(例如本次事件的触控点)。
  1. 触摸开始事件处理
function _onPointerStart(handler, e) {
  if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
    DomEvent.preventDefault(e); // 阻止默认行为(如滚动)
  }
  _handlePointer(handler, e);
}
  • 目的:在触摸开始时,阻止浏览器默认行为(如页面滚动),确保 Leaflet 能完全控制交互。
  • 关键点
    • 仅对触摸类型(非鼠标/触控笔)阻止默认行为。

总结

  • 核心逻辑
    Leaflet 通过抽象指针事件,将其转换为统一的触摸事件,解决了不同浏览器(尤其是旧版 IE)的兼容性问题,并支持多点触控操作。

  • ​ 设计亮点

    • 全局指针跟踪:通过文档级监听维护所有指针状态。
    • 事件结构模拟:将 PointerEvent 转换为类似 TouchEvent 的结构,统一上层逻辑。
    • 性能优化:仅在需要时(如首次 touchstart)添加全局监听,避免资源浪费。
  • 适用场景
    地图拖拽、缩放、绘制等需要处理复杂触控交互的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jinuss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值