本人開發的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端是鼠標按下觸發 mousedown 事件。移動鼠標的時候觸發 mouseover 事件,但移動過程當中,一旦光標離開了 event.srcElement
,就沒法觸發 mouseover 事件。鼠標釋放則觸發 mouseup 事件。
手指按下時,會觸發 touchstart 事件。此時手指不擡起進行移動的話,就會觸發 touchmove 事件,即使移動到 event.srcElement
的範圍外,依舊會觸發 touchmove 事件,此時的 srcElemnt 依舊是原來的節點,而不是當前手指位置對應的節點。手指擡起時,無論此時手指在哪裏,也和 touchmove 事件同樣,srcElement 不會指向手指位置的節點。
(1)移動端手指按下並提起(沒有較大位移),依次觸發如下事件:
(2)移動端手指按下並大幅移動,而後提起,觸發的事件順序:
這裏咱們會看到,touchmove 事件的觸發會致使 mouse 事件沒法觸發。
事實上,只要給 touchstart 或 touchend 事件添加一行 event.preventDefault()
,當發生 touch 行爲時,mouse 事件就沒法觸發。touchmove 默認就能阻止 mouse 事件的觸發,但在發生 touch 行爲中,它不必定會觸發(須要有較大偏移量)。
touchEvent 對象,是 touch 事件觸發時產生的事件對象。它和 mouse 事件對象有一些不一樣的地方。首先手指的當前位置,是存放在一個名爲 touches
的數組裏的。該數組保存着 touch 對象。touch 對象有如 clentX, clientY 等不少座標相關的屬性,惟獨沒有 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;
}
複製代碼
一開始我就是使用這種方案的,但它有一個問題。若是對於使用普通電腦的用戶來講,這是沒問題的,但若是用的是像 surface 這種能夠觸屏的筆記本,那就會有問題。由於經過觸屏點擊後,會觸發 touch 事件,但 mouse 事件不必定會觸發。即便觸發了 mouse 事件,也是經過擡起手指觸發的,並且是 mouseover -> mousedown -> mouseup 的順序,且 mouseover 在手指按下移動過程當中是沒法觸發的。
這種方案有很嚴重的問題,它沒法勝任 觸屏筆記本 的狀況。
具體作法是,在 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 編輯操做了。