响应用户输入可以说是我们界面开发的核心。为了构建响应式Web产品,理解触摸,鼠标,指针,键盘如何和浏览器一起工作是关键。你很有可能已经在移动浏览器上经历了 300毫秒的延迟 或者在纠结于 触摸滚动导致页面卡顿 。
在这篇文章中,我们将介绍事件级联并且利用这些知识来实现一个支持多种输入法的点击事件而不违反像Opera Mini这些代理浏览器事件规则。
在当今网络交互中有三个主要方法,数字光标(鼠标),触觉(直接触摸或笔)和键盘。在JavaScript中我们必须通过 触摸事件 , 鼠标事件 , 指针事件 和 键盘事件 获得这些。在这篇文章中,我们主要关注的是以触摸和鼠标为基础的交互,虽然有些事件有标准的键盘基础互动。例如 click
和 submit
事件。
你应该已经准备好了关于 mouse
和 touch
事件的事件句柄。在过去不久的时候,我们有一个类似于这样的推荐方法:
/** DO NOT EVER DO THIS! */ $('a', ('ontouchstart' in window) ? 'touchend' : 'click', handler);
微软已经首先致力于创造更好的,更有未来的“指针事件”规范。指针事件是现在在W3C拟议的一个抽象的输入机制。鼠标指针事件给用户代理很大的灵活性,它在少于一个事件的系统下可以容纳大量的输入机制。鼠标,触摸和手写笔是如今很容易浮现在脑海里的例子,尽管延伸至 Myo 和 Ring 也是有可能实现的。而Web开发人员似乎很兴奋这一点,尽管并不是所有的浏览器的工程师们也有同感。就像苹果和谷歌已经决定不在这个时候落实指针事件。
谷歌的决定并不一定是最终的,但现在对指针事件并没有积极的工作。我们通过polyfills输入和使用指针事件和替代解决方案将成为等式的一部分,可能最终起决定作用。苹果在2012年对指针事件做出了发言,不过我没有看到Safari的工程师有其他更进一步的公开回应
当用户在移动装置上点击元素时,浏览器将激活事件。这个动作通常会触发一系列如下事件: touchstart
→ touchend
→ mouseover
→ mousemove
→ mousedown
→ mouseup
→ click
。
这是由于web的向后兼容性。指针事件采取另一种方式,触发内联兼容事件: mousemove
→ pointerover
→ mouseover
→ pointerdown
→ mousedown
→ gotpointercapture
→ pointerup
→ mouseup
→ lostpointercapture
→ pointerout
→ mouseout
→ focus
→ click
。
事件规范允许用户代理们用不同的方式来实现兼容性的事件。Patrick Lauke和Peter-Paul Koch在这个话题上有着观法的参考资料,在这篇文章底部有链接到这些资源的链接。
下图显示了事件级联的操作流程:
请注意:此事件堆栈中有意的忽略了聚焦和失焦事件
事件级联在IOS设备上点击元素两次然后失去事件(图片: Stephen Davis )( 查看大图 )
事件级联在Android4.4以上设备上点击元素两次然后失去事件(图片: Stephen Davis )( 查看大图 )
事件级联在IE11(兼容触摸事件实施之前)上点击元素两次然后失去事件(图片: Stephen Davis )( 查看大图 )
因为浏览器工程师的工作,如今创建的大多数桌面Web网站“只是可以运行”。尽管级联看起来有点粗糙,但建立鼠标事件是我们以前通常工作的保守做法。
当然,这有一个陷阱。臭名昭著的 300
毫秒延迟是非常有名的,但是滚动之间的相互作用, touchmove
和 pointermove
事件,浏览器的渲染这些额外的问题。避免 300
毫秒的延迟是很容易的,如果:
<meta name="viewport" content="width=device-width">
来禁用延迟。 如果我们的目标是建立一个在用户体验上可以和本地平台竞争Web产品,那么我们就需要减少交互所带来的延迟。要做到这一点,我们必须建立原始事件( down
, move
和 up
),并创建自己的复合事件( click
, double-click
)。当然,我么仍然需要包括本机备用处理程序来得到广泛支持和辅助。
这样做需要大量的代码和知识。为了避免 300
毫秒(或任何长度)跨浏览器延迟,我么需要自己处理生命周期的全部交互。对于给定的 {type}down
事件,我么需要绑定所有事件,为完成该操作这是必要的。当交互完成后,我们会在需要自己清理除了其实事件的所有被接触绑定的事件。
网站开发人员,你是唯一一个知道页面是否应该放大或另一个双击事件是否必须等待的人。如果只有你需要回调推迟你应该允许一个预定的动作来延迟。
在下面的链接里,你会发现一个小的,无依赖点击案例来说明建立一个多输入、低延迟的点击事件所需的工作量。Polymer-gestures是一个为tap点击和其他事件生产开发的库。尽管是这个名字有Polymer,但是它是不依赖Polymer库而且很容易被隔离。
明确的说,实施这个从一开始来说就是一个坏主意,以下这些应该仅仅用于教育而不是用于生产环境。用于生产环境的库已经存在,例如: FastClick , polymer-gestures 和 Hammer.js 。
在所有开始的地方绑定你的初始事件,这下面处理多输入的模式是被认为一种保险的方式。
/** + If there are pointer events, let the platform handle the input + mechanism abstraction. If not, then it’s on you to handle + between mouse and touch events. */ if (hasPointer) { tappable.addEventListener(POINTER_DOWN, tapStart, false); clickable.addEventListener(POINTER_DOWN, clickStart, false); } else { tappable.addEventListener('mousedown', tapStart, false); clickable.addEventListener('mousedown', clickStart, false); if (hasTouch) { tappable.addEventListener('touchstart', tapStart, false); clickable.addEventListener('touchstart', clickStart, false); } } clickable.addEventListener('click', clickEnd, false);
绑定 touch
事件需要和渲染性能妥协,即使它们没有做任何事,但为了减少这种影响,通常推荐在处理程序开始时候绑定跟踪事件。别忘了在完成你的事件处理后要清理自己的环境和解绑跟踪事件。
/** + On tapStart we want to bind our move and end events to detect + whether this is a “tap” action. + @param {Event} event the browser event object */ function tapStart(event) { // bind tracking events. “bindEventsFor” is a helper that automatically // binds the appropriate pointer, touch or mouse events based on our // current event type. Additionally, it saves the event target to give // us similar behavior to pointer events’ “setPointerCapture” method. bindEventsFor(event.type, event.target); if (typeof event.setPointerCapture === 'function') { event.currentTarget.setPointerCapture(event.pointerId); } // prevent the cascade event.preventDefault(); // start our profiler to track time between events set(event, 'tapStart', Date.now()); } /** + tapEnd. Our work here is done. Let’s clean up our tracking events. + @param {Element} target the html element + @param {Event} event the browser event object */ function tapEnd(target, event) { unbindEventsFor(event.type, target); var _id = idFor(event); log('Tap', diff(get(_id, 'tapStart'), Date.now())); setTimeout(function() { delete events[_id]; }); }
剩下的这些代码应该能够很好的自我解释,事实上,它有很多簿记,实现自定义手势要求你用浏览器事件系统来紧密合作。为了挽救你的受伤和心痛,不要在你自己的代码库里做事情。相反你应该建立或使用一个强大的抽象,例如 Hammer.js ,jQuery polyfill的 Pointer Events 或者 polymer-gestures 。
一些曾经很清楚的事件现在却是有歧义的,以前 click
事件用来指有且只有一件事,但是现在在触摸屏上面需要辨别是双击、滚动或者其他操作系统的手势。
好消息是,我们现在明白了很多用户的操作习惯和浏览器的响应之间的事件级联和相互作用,通过在工作中认识的原语,我们自己能够在我们的项目中为我们的用户和Web的未来做出更好的决策。
你在构建多设备的网站时,有遇到什么意想不到的问题?你采取什么样的方法来解决Web上众多的交互模式?
本文根据 @Dustan Kasten 的《 Browser Input Events: Can We Do Better Than The Click? 》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: http://www.smashingmagazine.com/2015/03/20/better-browser-input-events 。