ToC
设备类型判断
首先我们要做的事是判断当前的设备类型,如果本身就是移动端,那我们就不需要做任何处理,使用因为我们本身就是监听的 touch 事件。判断的方式也很简单,只需要 'ontouchstart' in window
就能知道是移动端(或者 Pad 端)还是 PC 端:
1(() => {2 // 如果是 ssr 渲染则不 hack 事件3 if (typeof window === 'undefined') {4 return5 }6
7 // 如果支持这个事件类型则不做处理, 否则才需要将 mouse 事件转换为 touch8 if ('ontouchstart' in window) {9 return10 }1 collapsed line
11})();
需要我们做处理的事件类型其实只有 mousedown
mousemove
mouseup
,而 mouseenter
mouseleave
mouseover
mouseout
是不需要处理的,因为这四个类型根本没法模拟,所以我们主要专注于点击类的事件即可。我们先从 mousedown
开始
mousedown to touchstart
首先我们需要监听全局的 mouse 事件,然后需要把 mouse 事件对象参数转换为 touch 事件的对象参数类型,最后再触发 touch 事件:
1// ...2
3// 保存事件触发时的target dom4let eventTarget5
6function onMouse (ev) {7 eventTarget = ev.target8
9 // 因为三个事件类型最终都需要转换成参数类型, 所以封装成函数方便调用10 triggerTouchEvent('touchstart', ev)16 collapsed lines
11}12
13// 转换事件类型14function triggerTouchEvent (eventName, mouseEvent) {15 const touchEvent = new Event(eventName, { bubbles: true, cancelable: true })16
17 touchEvent.altKey = mouseEvent.altKey18 touchEvent.ctrlKey = mouseEvent.ctrlKey19 touchEvent.metaKey = mouseEvent.metaKey20 touchEvent.shiftKey = mouseEvent.shiftKey21
22 eventTarget.dispatchEvent(touchEvent)23}24
25// 监听全局事件从而转换事件对象26window.addEventListener('mousedown', onMouse, true)
这样就支持了将 mousedown
事件转换为 touchstart
事件,现在已经可以在 PC 上已经使用简易的 touchstart
事件类型来作为 mousedown
的替代了,但这样还不够,我们还需要处理 mousemove
mouseup
类型,在上述代码中我们将时间类型写死了,所以接下来我们对其做一点优化,比如把 onMouse
转换为高阶函数,同时增加对其他时间类型的处理代码。
mousemove|mouseup to touchmove|touchend
1// ...2
3// 增加一个变量用于判断是否按下4// 如果被按下才触发 move 事件, 否则 mousemove 事件会一直触发5let initiated = false6
7// 改造 onMouse 函数8function onMouse (eventType) {9 return function (ev) {10 if (ev.type === 'mousedown') {28 collapsed lines
11 initiated = true12 } else if (ev.type === 'mouseup') {13 initiated = false14 } else if (ev.type === 'mousemove' && !initiated) { // 没有按下则不触发 move 事件15 return16 }17
18 if (19 ev.type === 'mousedown' || // 按下时更新20 !eventTarget || // 如果事件对象不存在21 (eventTarget && !eventTarget.dispatchEvent) // 如果当前对象无法触发事件则更新22 ) {23 eventTarget = ev.target24 }25
26 triggerTouchEvent(eventType, ev)27
28 // 鼠标抬起时重置对象29 if (ev.type === 'mouseup') {30 eventTarget = null31 }32 }33}34
35// ...36// 增加对其他两种事件类型的支持37window.addEventListener('mousemove', onMouse('touchmove'), true)38window.addEventListener('mouseup', onMouse('touchend'), true)
这样就算支持了三种事件类型,但是这还不够好,因为移动端可能会有多点触控,这个能力是我们现在还不具备的,所以我们还需要 hack 一下多触控点:
multi point touch
1// ...2
3function triggerTouchEvent (eventName, mouseEvent) {4 // ...5 touchEvent.touches = getActiveTouches(mouseEvent)6 touchEvent.targetTouches = getActiveTouches(mouseEvent)7 touchEvent.changedTouches = createTouchList(mouseEvent)8
9 eventTarget.dispatchEvent(touchEvent)10}45 collapsed lines
11
12const Touch = function Touch (target, identifier, pos, deltaX, deltaY) {13 deltaX = deltaX || 014 deltaY = deltaY || 015
16 this.identifier = identifier17 this.target = target18 this.clientX = pos.clientX + deltaX19 this.clientY = pos.clientY + deltaY20 this.screenX = pos.screenX + deltaX21 this.screenY = pos.screenY + deltaY22 this.pageX = pos.pageX + deltaX23 this.pageY = pos.pageY + deltaY24}25
26function TouchList () {27 const touchList = []28
29 touchList.item = function (index) {30 return this[index] || null31 }32
33 // specified by Mozilla34 touchList.identifiedTouch = function (id) {35 return this[id + 1] || null36 }37
38 return touchList39}40
41function createTouchList (mouseEv) {42 const touchList = TouchList()43
44 touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0))45
46 return touchList47}48
49function getActiveTouches (mouseEvent) {50 if (mouseEvent.type === 'mouseup') {51 return TouchList()52 }53
54 return createTouchList(mouseEvent)55}
忽略事件
现在事件已经比较完善了,但毕竟是模拟的,我们可能在某些场景下会不希望模拟的事件触发,那么我们可以增加一个选项:
1function onMouse (eventType) {2 // ...3
4 // 当父级元素上设置了 data-no-touch-simulate 属性的时候则不触发模拟事件5 if (eventTarget.closest('[data-no-touch-simulate]') == null) {6 triggerTouch(eventType, ev);7 }8
9 if (ev.type === 'mouseup') {10 eventTarget = null;2 collapsed lines
11 }12}
完整代码
1(() => {2 if (typeof window === 'undefined') {3 return4 }5
6 if ('ontouchstart' in window) {7 return8 }9
10 let eventTarget93 collapsed lines
11 let initiated12
13 function onMouse (eventType) {14 return function (ev) {15 if (ev.type === 'mousedown') {16 initiated = true17 } else if (ev.type === 'mouseup') {18 initiated = false19 } else if (ev.type === 'mousemove' && !initiated) {20 return21 }22
23 if (24 ev.type === 'mousedown' || // 按下时更新25 !eventTarget || // 如果事件对象不存在26 (eventTarget && !eventTarget.dispatchEvent)27 ) {28 eventTarget = ev.target29 }30
31 if (eventTarget.closest('[data-no-touch-simulate]') == null) {32 triggerTouch(eventType, ev);33 }34
35 if (ev.type === 'mouseup') {36 eventTarget = null37 }38 }39 }40
41 function triggerTouchEvent (eventName, mouseEvent) {42 const touchEvent = new Event(eventName, { bubbles: true, cancelable: true })43
44 touchEvent.altKey = mouseEvent.altKey45 touchEvent.ctrlKey = mouseEvent.ctrlKey46 touchEvent.metaKey = mouseEvent.metaKey47 touchEvent.shiftKey = mouseEvent.shiftKey48
49 touchEvent.touches = getActiveTouches(mouseEvent)50 touchEvent.targetTouches = getActiveTouches(mouseEvent)51 touchEvent.changedTouches = createTouchList(mouseEvent)52
53 eventTarget.dispatchEvent(touchEvent)54 }55
56 const Touch = function Touch (target, identifier, pos, deltaX, deltaY) {57 deltaX = deltaX || 058 deltaY = deltaY || 059
60 this.identifier = identifier61 this.target = target62 this.clientX = pos.clientX + deltaX63 this.clientY = pos.clientY + deltaY64 this.screenX = pos.screenX + deltaX65 this.screenY = pos.screenY + deltaY66 this.pageX = pos.pageX + deltaX67 this.pageY = pos.pageY + deltaY68 }69
70 function TouchList () {71 const touchList = []72
73 touchList.item = function (index) {74 return this[index] || null75 }76
77 touchList.identifiedTouch = function (id) {78 return this[id + 1] || null79 }80
81 return touchList82 }83
84 function createTouchList (mouseEv) {85 const touchList = TouchList()86
87 touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0))88
89 return touchList90 }91
92 function getActiveTouches (mouseEvent) {93 if (mouseEvent.type === 'mouseup') {94 return TouchList()95 }96
97 return createTouchList(mouseEvent)98 }99
100 window.addEventListener('mousedown', onMouse('touchstart'), true)101 window.addEventListener('mousemove', onMouse('touchmove'), true)102 window.addEventListener('mouseup', onMouse('touchend'), true)103})()