Can We Do Better Than The Click?

Categories:  Interactive

原文来自:Browser Input Events: Can We Do Better Than The Click?

响应用户的输入可以说是我们做界面开发的核心。为了构建可响应的Web产品,理解触摸、鼠标、指针、键盘操作和浏览器的工作原理是解决问题的关键。你可能在手机浏览器中经历过300-millisecond delay(300 ms延迟)或者wrestled with touchmove versus scrolling(触摸滚动导致页面卡顿)。

在本文中,我们将介绍事件级联和使用这些知识来实现演示一个tap点击事件,支持多种输入方法但是又没有违反像Opera Mini这一类的代理浏览器规则。

注:tap也翻译为点击,tap和click都是在点击时候触发,但在手机Web端click会有200~300ms延迟,为了避免歧义,下文将” tap event“翻译成“tap点击事件“

概况

目前被用于与Web交互的3种主要的交互方式是:数字光标(鼠标)、触觉(直接触摸或者手写笔)、键盘。在JavaScript中我们可以通过触摸事件,鼠标事件,指针事件键盘事件来使用它们。在这篇文章中我们主要讨论触摸和基于鼠标的交互,虽然这里有键盘事件已经形成标准,如click和submit事件。

你很有可能已经实现过鼠标和触摸事件的处理程序,在过去我们推荐的做法是这样:

/** DO NOT EVER DO THIS! */
$('a', ('ontouchstart' in window)?'touchend':'click',handler);

微软已经负责创建了一个更好的,更有前途的“指针事件”的事件模型规范。指针事件是一个抽象的输入机制,现在是W3C推荐标准。指针事件给用户代理(UA)带来灵活性,给一个事件系统提供丰富的输入机制。鼠标、触摸、手写笔是今天很容易想到的输入方式,但实现延伸到myo(肌)和Ring(环)也是可实现的。虽然Web开发者对这些似乎很兴奋,但并不是所有的浏览器开发者也会这样以为。就像苹果和谷歌现在就没有打算来实现指针事件。
谷歌的这个决定并不是他们最终的决定,但是目前在指针事件上没有活跃的工作。我们通过polyfills输入和使用指针事件和替代解决方案将成为等式的一部分,可能最终起决定作用。苹果在2012年发表申明反对指针事件,我现在没有从Safari的工程师那里得到任何公众回应。

事件级联

当用户在移动设备上面tap点击一个元素时,浏览器会触发一系列事件:

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维护着关于这一主题的广泛的参考材料(资源链接位于文章底部)。 以下图片显示下列行为的事件串:

  1. 首次tap点击一个元素,
  2. 第二次tap点击一个元素,
  3. tap点击关闭元素,

请注意:为了适应这个栈这个事件栈故意忽略了focus和blur事件。

IOS Touch Events
在IOS设备上的tap点击一个元素两次和松开的事件级联

Android4.4 Touch Events
在 Android 4.4 设备上的tap点击一个元素两次和松开的事件级联

Android4.4 Touch Events
在Internet Explorer 11(在兼容的触摸事件实现)上tap点击元素两次然后松开的事件级联

应用事件级联

由于浏览器工程师的工作,大多数现在建成的桌面网站“只是可以工作而已”。尽管级联看起来有点粗糙,但建立鼠标事件是我们以前通常工作的保守做法。
当然,300ms的延迟问题比起在滚动和touchmove和pointermove事件相互作用,还有浏览器渲染问题这些额外的问题更加臭名昭著。避免300 ms的延迟很容易,如果:

  • 我们只为现代的Android和桌面端的Chrome优化,通过使用来启动禁用延迟这一功能。
  • 我们只为IOS设备优化,用户有一个明确的点击,不是那种快速的点击或者长按,仅仅是一个元素良好的、正常的、明确的点击。

如果我们的目标是建立一个能在用户体验和本地应用媲美的Web应用,那么我们需要减少交互响应的延迟。为了达到这个目标,我们需要在原始事件(如down,move,up)上创建我们属于自己的复合事件(click,double-click),当然我们还需要对本地事件的回退处理提供广泛和可行的支持。

做这些需要不少的代码和知识。为了避免在浏览器中的300ms延迟,我们需要处理全部生命周期内的自我交互。对于一个给定{类型}的down事件,我们需要绑定所有必需的事件来完成这个功能,当交互已经完成后,我们需要清理自己解绑的起始事件。

网站开发人员,你是唯一一个知道页面是否应该放大或另一个双击事件是否必须等待的人。如果只有你需要回调推迟你应该允许一个预定的动作来延迟。

在接下来链接中,你会发现一个小的、相互间无依赖的tap事件Demo来创造一个多输入、低延迟的tap点击事件。Polymer-gestures是一个为tap点击和其他事件生产开发的库。尽管是这个名字有Polymer,但是它是不依赖Polymer库而且很容易被隔离。

明确的说,实施这个从一开始来说就是一个坏主意,以下这些应该仅仅用于教育而不是用于生产环境。用于生产环境的库已经存在,例如: FastClick,polymer-gesturesHammer.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上众多的交互模式?

参考链接

Buy Me a Coffee !
Disqus is climbing the Great Fire Wall of China, Maybe She needs a ladder.   🤦🏼‍️ 🤷🏼‍️
Read More

深入浅出Nodejs读书笔记

【2015-03-01】今天终于把朴灵老师写的《深入浅出Node.js》给学习完了, 这本书不是一本简单的Node入门书籍,它没有停留在Node介绍或者框架、库的使用层面上,而是从不同的视角来揭示Node自己内在的特点和结构。建议有一定Node基础或者做过Node方面的小项目的同学阅读,看完以后你的思维会有很奇特的碰撞,我看的时候就常常会有这样的想法:“哦,原来这个功能是这样实现的哦”。下面这篇文章是我第二次阅读《深入浅出Node.js》的一些学习记录,并且通过百度脑图这个工具来画出思维导图,每天将自己的学习总结写在这篇文章下面 ...