svg 编辑器的点击事件兼容pc端和移动端方案

本人开发的svg编辑器本来只是针对 PC 端的。在pc端浏览器,能够正常地进行矢量图形地绘制,但在移动端没法使用,在画布上点击没有什么效果。但后来但愿能够在一些屏幕大的移动设备上也能简单的使用,作了一些兼容方案,因而总结写下了这篇点击事件的pc端和移动端的适配处理方案分享。数组

基本架构

和 Adobe Illustrator 同样,这款 svg 编辑器也提供了不少工具。点击工具图标,就切换到对应的模式。浏览器

svg 编辑器目前支持下面工具:bash

  • 选择工具
  • 路径编辑工具
  • 铅笔工具
  • 钢笔(路径)工具
  • 添加锚点工具
  • 删除锚点工具
  • 锚点工具

每一个工具对应一个 action 对象。这个 action 对象有下面的属性(为了简明,这里我写成 ts 的接口形式,不过我 ts 不太熟,下面的语法可能会有问题,看懂就行):架构

interface EventHandler {
  (e: object): void;
}

interface Action {
    name: string;               // action 的名称,如select,path等
    bindEvents: string[];       // 字符串数组,记录须要绑定的事件名。如 mousedown
    unbind: function            // 解绑事件钩子
    mousedown ?: EventHandler   // 事件名:事件响应函数。
    ...
}
复制代码

举个例子:编辑器

let aciton: Action = {
    name: 'select',
    bindEvents: ['mousedown', 'mousemove', 'mouseup'],
    unbind(){ /* 销毁在 select 模式下才会存在的对象 */}
    mousedown(e) { /* 鼠标按下事件响应函数 */ },
    mousemove(e) {},
    mouseup(e) {},
}
复制代码

咱们用一个名为 actionsManager.js 的文件来管理这些 action,来进行工具的切换。具体代码就不说了,简单说下它作了什么东西:svg

首先,咱们会将这些 action 导入(即import)到该模块下,而后保存到一个 actions 对象中。名为 bindEvents 的函数负责 action 的切换。为了实现切换功能,咱们还须要一个 curAction 变量,来指向当前正在使用的 action。函数

这样咱们点击切换的时候,就能够经过遍历当前 action 的 bindEvents 数组的对应的全部事件响应函数,经过 removeEventListener 方法一一取消绑定。而后咱们再遍历新的 action 的 bindEvents 数据,使用 addEventListener 来 绑定事件响应函数。工具

自此,编辑器的工具切换的实现大体说完。下面开始说明如何适配移动端的点击事件post

移动端点击事件适配

在对代码进行改造前,咱们先来了解一下移动端和pc端的点击行为的异同。移动端有独有的 touch 事件,虽然移动端是能够触发 mouse 事件,但和 pc 端有点不一样。ui

PC端的点击行为

pc端是鼠标按下触发 mousedown 事件。移动鼠标的时候触发 mouseover 事件,但移动过程当中,一旦光标离开了 event.srcElement,就没法触发 mouseover 事件。鼠标释放则触发 mouseup 事件。

移动端的点击行为

手指按下时,会触发 touchstart 事件。此时手指不抬起进行移动的话,就会触发 touchmove 事件,即使移动到 event.srcElement 的范围外,依旧会触发 touchmove 事件,此时的 srcElemnt 依旧是原来的节点,而不是当前手指位置对应的节点。手指抬起时,无论此时手指在哪里,也和 touchmove 事件同样,srcElement 不会指向手指位置的节点。

移动端事件触发顺序

(1)移动端手指按下并提起(没有较大位移),依次触发如下事件:

  1. touchstart
  2. touchend
  3. mousemove
  4. mousedown
  5. mouseup
  6. click

(2)移动端手指按下并大幅移动,而后提起,触发的事件顺序:

  1. touchstart
  2. touchmove
  3. touchend

这里咱们会看到,touchmove 事件的触发会致使 mouse 事件没法触发。

事实上,只要给 touchstart 或 touchend 事件添加一行 event.preventDefault(),当发生 touch 行为时,mouse 事件就没法触发。touchmove 默认就能阻止 mouse 事件的触发,但在发生 touch 行为中,它不必定会触发(须要有较大偏移量)。

touchEvent

touchEvent 对象,是 touch 事件触发时产生的事件对象。它和 mouse 事件对象有一些不一样的地方。首先手指的当前位置,是存放在一个名为 touches 的数组里的。该数组保存着 touch 对象。touch 对象有如 clentX, clientY 等不少坐标相关的属性,惟独没有 offsetX/offsetY 属性。

代码改造

下面咱们就来对原有的代码进行改造。

移动端的 offsetX/offsetY 计算

touch 事件并不提供 offsetX 和 offsetY 属性。然而这两个属性对一款 svg 来讲是必不可少的属性。咱们须要用到这两个属性,来定位光标在画布上的位置,来绘制路径等图案。

为此咱们须要写一个方法,将 pageX/pageY(光标距离页面左上角的位置)转换为 offsetX/offsetY,并做为 e 的补充属性。它们的关系是:

offsetX + left = pageX;
offsetY + top = pageY;
复制代码

left(top) 表示绑定 touch 事件的元素,距离页面左上角的距离。

const handleEventObj = function(e) {
    const LEFT = 256;    // 须要根据实际状况进行计算。
    const TOP = 60;
    
    const touch = e.changedTouches[0];
    const offsetX = touch.pageX - LEFT + workarea.scrollLeft;
    const offsetY = touch.pageY - TOP + workarea.scrollTop;
    Object.assign(e, {offsetX, offsetY});
    return e;
}

复制代码

改造工具切换函数

方案1:判断pc端仍是移动端,决定是绑定 touch 事件仍是 mouse 事件。

一开始我就是使用这种方案的,但它有一个问题。若是对于使用普通电脑的用户来讲,这是没问题的,但若是用的是像 surface 这种能够触屏的笔记本,那就会有问题。由于经过触屏点击后,会触发 touch 事件,但 mouse 事件不必定会触发。即便触发了 mouse 事件,也是经过抬起手指触发的,并且是 mouseover -> mousedown -> mouseup 的顺序,且 mouseover 在手指按下移动过程当中是没法触发的。

这种方案有很严重的问题,它没法胜任 触屏笔记本 的状况。

方案2:同时绑定 touch 事件和 mouse 事件

具体作法是,在 actionsManager.js 引入全部 action 的时候,就自动遍历全部 action 的 bindEvents。若是有 mousedown/mouseover/mouseup 事件响应函数,就额外添加对应的 touchstart/touchmove/touchend 事件响应函数。

具体代码以下:

const map = {
    mousedown: 'touchstart',
    mousemove: 'touchmove',
    mouseup: 'touchend',
};

if (['mousedown', 'mousemove', 'mouseup'].includes(eventName)) {
    action.bindEvents.push(map[eventName])
    action[map[eventName]] = function(e) {
        e.preventDefault();     // 这个很重要,它能阻止后续的 mouse 事件。
        handleEventObj(e);      // event 对象添加 offsetX ofsetY 属性。
        action[eventName](e);
    }
}
复制代码

这里的 eventName 就是事件名。

这里举个例子,假设咱们有个 selectAction 对象,它有一个 mousedown 事件,咱们就给 selectAction 添加一个 touchstart 方法。这个 touchstart 方法会阻止默认事件,这样就能阻止 mouse 事件触发,而后给 event 对象添加 offsetX/offsetY,把 event 传入 action.mousedown 方法。

这样,在 ipad 等移动端配合触屏笔,也能够进行简单的 svg 编辑操做了。

参考

掘金——touchstart与click不得不说的故事

相关文章
相关标签/搜索