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

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

基本架构

和 Adobe Illustrator 一样,这款 svg 编辑器也提供了很多工具。点击工具图标,就切换到对应的模式。

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

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

每个工具对应一个 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,来进行工具的切换。具体代码就不说了,简单说下它做了什么东西:

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

这样我们点击切换的时候,就可以通过遍历当前 action 的 bindEvents 数组的对应的所有事件响应函数,通过 removeEventListener 方法一一取消绑定。然后我们再遍历新的 action 的 bindEvents 数据,使用 addEventListener 来 绑定事件响应函数。

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

移动端点击事件适配

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

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不得不说的故事

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容