统一触摸和鼠标:指针事件将如何轻松实现跨浏览器触摸支持





5.00/5 (3投票s)
统一触摸和鼠标:指针事件将如何轻松实现跨浏览器触摸支持
开发者经常问我:“手机和平板电脑上有这么多触摸设备,我该从哪里开始?”和“构建触摸输入的最简单方法是什么?”简而言之:“这很复杂。”当然,在现代触摸设备浏览器中,或者作为旧浏览器的后备方案,一定有一种更统一的方式来处理 Web 上的多点触摸输入。在本文中,我想向您展示一些使用 MSPointers 进行的浏览器实验——一项新兴的多点触摸技术和 polyfills,可以使跨浏览器支持变得不那么复杂。您可以轻松地在自己的网站上尝试和使用这类代码。
首先,Web 上有很多触摸技术正在发展——为了获得浏览器支持,您需要查看 iOS 触摸事件模型和 W3C 鼠标事件模型,另外还要了解 MSPointers,以支持所有浏览器。然而,标准化正在获得越来越多的支持(和意愿)。9 月份,微软向 W3C 提交了 MSPointers 以便标准化,昨天,我们已经达到了最终征求意见稿:http://www.w3.org/TR/pointerevents。MS Open Tech 团队最近还发布了一个 适用于 Webkit 的 Pointer Events 初始原型。
我进行 MSPointers 实验的原因并非基于设备份额——而是因为微软处理基本输入的方式与 Web 上现有的方式大不相同,并且值得一看它可能成为什么样子。区别在于,开发人员可以编写更抽象的输入形式,称为“Pointer”(指针)。指针可以是鼠标光标、笔、手指或多个手指在屏幕上的任何接触点。这样您就不必浪费时间为每种输入类型单独编码。
概念
我们将从回顾运行在 Internet Explorer 10 中的应用程序开始,它公开了 MSPointer 事件 API,然后介绍支持所有浏览器的解决方案。之后,我们将看到如何利用 IE10 手势服务,这些服务将帮助您轻松地在 JavaScript 代码中处理触摸。由于 Windows 8 和 Windows Phone 8 共享相同的浏览器引擎,因此这两个平台的代码和概念是相同的。此外,本文中您学到的所有关于触摸的内容都将帮助您在使用 HTML5/JS 构建的 Windows 应用商店应用中执行相同的任务,因为这同样是正在使用的引擎。
MSPointer 的理念是让您通过单一代码库,使用与您已知的经典鼠标事件相匹配的模式来处理鼠标、笔和触摸设备。事实上,鼠标、笔和触摸具有一些共同的属性:您可以用它们移动指针,并且可以用它们点击元素。那么,让我们通过完全相同的代码来处理这些场景!指针将聚合这些共同属性,并以类似于鼠标事件的方式公开它们。
最明显的共同事件是:MSPointerDown、MSPointerMove 和 MSPointerUp,它们直接映射到等效的鼠标事件。您将获得屏幕的 X 和 Y 坐标作为输出。
您还有特定的事件,例如:MSPointerOver、MSPointerOut、MSPointerHover 或 MSPointerCancel
当然,也可能存在您希望以不同于默认鼠标行为的方式处理触摸的情况,以便提供不同的用户体验。此外,借助多点触摸屏,您可以轻松地让用户旋转、缩放或平移某些元素。笔/触控笔甚至可以为您提供鼠标无法提供的压力信息。Pointer Events 仍将聚合这些差异,并允许您为每种设备的特定功能构建自定义代码。
注意:当然,如果您拥有 Windows 8/RT 设备上的触摸屏,或者您正在使用 Windows Phone 8,最好在这些设备上测试以下嵌入的示例。不过,您也可以有一些选择
- 使用随免费 Visual Studio 2012 Express 开发工具一起提供的 Windows 8 模拟器,获得第一级的体验。有关此工作原理的更多详细信息,请阅读本文:使用 Windows 8 模拟器和 VS 2012 调试 IE10 触摸事件和您的响应式设计。
- 还可以看看 这个视频,在文章末尾也可以找到其他格式。该 视频演示了下方所有示例 在支持触摸、笔和鼠标的 Windows 8 平板电脑上的效果。
- 如果您无法访问 Windows 8 设备,可以使用像 BrowserStack 这样的虚拟跨浏览器测试服务进行交互式测试。感谢 Internet Explorer 团队在 modern.IE 上提供,您可以 免费使用 BrowserStack 3 个月。
处理简单的触摸事件
步骤 1:在 JS 中不做任何操作,只添加一行 CSS
让我们从基础开始。您可以轻松地将任何处理鼠标事件的现有 JavaScript 代码,在 Internet Explorer 10 中使用笔或触摸设备,它们将按原样工作。实际上,如果您没有在代码中处理 Pointer Events,IE10 会将鼠标事件作为最后的手段触发。这就是为什么,即使开发人员从未想过有一天会这样,您也可以用手指“点击”按钮或任何网页上的元素。因此,任何注册到 mousedown 和/或 mouseup 事件的代码都无需修改即可工作。但 mouvemove 呢?
让我们回顾一下默认行为来回答这个问题。例如,让我们看这段代码
<!DOCTYPE html> <html> <head> <title>Touch article sample 1</title> </head> <body> <canvas id="drawSurface" width="400px" height="400px" style="border: 1px dashed black;"> </canvas> <script> var canvas = document.getElementById("drawSurface"); var context = canvas.getContext("2d"); context.fillStyle = "rgba(0, 0, 255, 0.5)"; canvas.addEventListener("mousemove", paint, false); function paint(event) { context.fillRect(event.clientX, event.clientY, 10, 10); } </script> </body> </html>
它通过跟踪鼠标移动,在 HTML5 canvas 元素内绘制一些 10 像素 x 10 像素的蓝色方块。
您会看到,当您在 canvas 元素内移动鼠标时,它会绘制一系列蓝色方块。但是,使用触摸代替,它只会绘制一个独特的方块,位于您点击 canvas 元素的精确位置。一旦您尝试在 canvas 元素中移动手指,浏览器就会尝试在页面中平移,因为这是定义的默认行为。
然后,您需要指定您希望覆盖浏览器的默认行为,并告诉它将触摸事件重定向到您的 JavaScript 代码,而不是尝试解释它们。为此,请定位您页面中不再需要对默认行为做出反应的元素,并对其应用此 CSS 规则
-ms-touch-action: auto | none | manipulation | double-tap-zoom | inherit;
您会发现各种可用的值,具体取决于您想过滤的内容。您可以在本文中找到这些值的描述:构建触摸友好型网站指南
经典用例是当您的页面中有一个地图控件时。您希望让用户在地图区域内平移和缩放,但保持页面其余部分的默认行为。在这种情况下,您只需将此 CSS 规则(-ms-touch-action: manipulation)应用于显示地图的 HTML 容器。
在我们的例子中,添加此 CSS 块
<style> #drawSurface { -ms-touch-action: none; /* Disable touch behaviors, like pan and zoom */ } </style>
现在,当您在 canvas 元素内移动手指时,它的行为就像鼠标指针一样。这太棒了!但是您很快会问自己这个问题:为什么这段代码只跟踪 1 个手指?这是因为我们只是在利用 IE10 提供非常基本的触摸体验的最后一种方式:将您的一个手指映射到模拟鼠标。据我所知,我们一次只使用 1 个鼠标。所以,使用这种方法,1 个鼠标 == 最多 1 个手指。那么,如何处理多点触摸事件呢?
步骤 2:使用 MSPointer 事件代替鼠标事件
采用您现有的任何代码,并将对“mousedown/up/move”的注册替换为“MSPointerDown/Up/Move”,您的代码将在 IE10 中直接支持多点触摸体验!
例如,在前面的示例中,更改此行代码
canvas.addEventListener("mousemove", paint, false);
为这行
canvas.addEventListener("MSPointerMove", paint, false);
您现在可以绘制任意数量的方块系列,只要您的屏幕支持触摸点!更好的是,相同的代码适用于触摸、鼠标和笔。例如,这意味着您可以使用鼠标绘制一些线条,同时用手指绘制其他线条。
如果您想根据输入类型更改代码的行为,可以通过 pointerType
属性值进行测试。例如,假设我们想为手指绘制 10 像素 x 10 像素的红色方块,为笔绘制 5 像素 x 5 像素的绿色方块,为鼠标绘制 2 像素 x 2 像素的蓝色方块。您需要将之前的处理程序(paint 函数)替换为这个
function paint(event) { if (event.pointerType) { switch (event.pointerType) { case event.MSPOINTER_TYPE_TOUCH: // A touchscreen was used // Drawing in red with a square of 10 context.fillStyle = "rgba(255, 0, 0, 0.5)"; squaresize = 10; break; case event.MSPOINTER_TYPE_PEN: // A pen was used // Drawing in green with a square of 5 context.fillStyle = "rgba(0, 255, 0, 0.5)"; squaresize = 5; break; case event.MSPOINTER_TYPE_MOUSE: // A mouse was used // Drawing in blue with a squre of 2 context.fillStyle = "rgba(0, 0, 255, 0.5)"; squaresize = 2; break; } context.fillRect(event.clientX, event.clientY, squaresize, squaresize); } }
如果您足够幸运,拥有一台支持这三种输入类型的设备(例如索尼 Duo 11、Microsoft Surface Pro 或您在 BUILD2011 期间拥有的三星平板电脑),您将能够看到三种基于输入类型的绘图。很棒,不是吗?
不过,这段代码仍然存在一个问题。它现在可以在 IE10 中正确处理所有类型的输入,但对于不支持 MSPointer 事件的浏览器(如 IE9、Chrome、Firefox、Opera 和 Safari)完全无效。
步骤 3:进行功能检测以提供回退代码
您可能已经意识到,处理多浏览器支持的最佳方法是进行功能检测。在我们的情况下,您需要测试这个
window.navigator.msPointerEnabled
请注意,这只能告诉您当前浏览器是否支持 MSPointer。它并不能告诉您是否支持触摸。要测试是否支持触摸,您需要检查 msMaxTouchPoints。
总之,要获得一段支持 IE10 中的 MSPointer 并能妥善回退到其他浏览器中的鼠标事件的代码,您需要类似这样的代码
var canvas = document.getElementById("drawSurface"); var context = canvas.getContext("2d"); context.fillStyle = "rgba(0, 0, 255, 0.5)"; if (window.navigator.msPointerEnabled) { // Pointer events are supported. canvas.addEventListener("MSPointerMove", paint, false); } else { canvas.addEventListener("mousemove", paint, false); } function paint(event) { // Default behavior for mouse on non-IE10 devices var squaresize = 2; context.fillStyle = "rgba(0, 0, 255, 0.5)"; // Check for pointer type on IE10 if (event.pointerType) { switch (event.pointerType) { case event.MSPOINTER_TYPE_TOUCH: // A touchscreen was used // Drawing in red with a square of 10 context.fillStyle = "rgba(255, 0, 0, 0.5)"; squaresize = 10; break; case event.MSPOINTER_TYPE_PEN: // A pen was used // Drawing in green with a square of 5 context.fillStyle = "rgba(0, 255, 0, 0.5)"; squaresize = 5; break; case event.MSPOINTER_TYPE_MOUSE: // A mouse was used // Drawing in blue with a square of 2 context.fillStyle = "rgba(0, 0, 255, 0.5)"; squaresize = 2; break; } } context.fillRect(event.clientX, event.clientY, squaresize, squaresize); }
步骤 4:支持所有触摸实现
如果您想更进一步,并支持所有浏览器和所有触摸实现,您有两个选择
- – 编写代码以并行处理两种事件模型,如本文所述:处理所有浏览器中的多点触摸和鼠标输入
- 只需添加对 HandJS 的引用,这是我朋友 David Catuhe 编写的出色 JavaScript polyfill 库,正如他在文章中所述:HandJS,一个支持所有浏览器 Pointer Events 的 Polyfill
正如我在本文开头提到的,微软最近向 W3C 提交了 MSPointer Events 规范以供标准化。W3C 创建了一个新的工作组,并且已经发布了一个基于微软提议的最终征求意见稿。MS Open Tech 团队最近还发布了一个 适用于 Webkit 的 Pointer Events 初始原型,您可能会对此感兴趣。
尽管 Pointer Events 规范尚未成为标准,但您仍然可以通过利用David 的 Polyfill 来实现支持它的代码,并为 Pointer Events 成为所有现代浏览器都实现的标准做好准备。使用 David 的库,事件将被传播到 IE10 中的 MSPointer,到基于 Webkit 的浏览器的 Touch Events,最后作为最后的手段传播到鼠标事件。这真是太酷了!查看他的文章以发现和理解它是如何工作的。请注意,这个 polyfill 在支持旧浏览器并优雅地回退到鼠标事件方面也将非常有用。
要了解如何使用此库,请参阅本文:使用 Hand.JS 创建适用于所有触摸模型的通用虚拟触摸摇杆,其中展示了如何使用 pointer events 编写一个虚拟触摸摇杆。有了 HandJS,它将在 IE10 上的 Windows 8/RT、Windows Phone 8、iPad/iPhone 和 Android 设备上使用完全相同的代码库运行!
识别简单手势
现在我们已经了解了如何处理多点触摸,让我们来看看如何识别简单的手势,如点击或按住元素,然后是更高级的手势,如平移或缩放元素。
IE10 提供了一个 MSGesture 对象,它将帮助我们。请注意,此对象目前特定于 IE10,并且不是 W3C 提交的一部分。与 MSCSSMatrix 元素(我们的 WebKitCSSMatrix 的等价物)结合使用,您会发现您可以非常简单地构建非常有趣的多点触摸体验。MSCSSMatrix 实际上代表一个 4x4 的齐次矩阵,它使文档对象模型 (DOM) 脚本能够访问 CSS 2D 和 3D 变换功能。但在使用它之前,让我们从基础开始。
基本概念是首先注册一个 MSPointerDown 的事件处理程序。然后在处理 MSPointerDown 的处理程序中,您需要选择您想发送到 MSGesture 对象的指针,以便它能检测到特定的手势。然后它将触发以下事件之一:MSGestureTap、MSGestureHold、MSGestureStart、MSGestureChange、MSGestureEnd、MSInertiaStart。然后,MSGesture 对象将把所有提交的指针作为输入参数,并在它们之上应用一个手势识别器,以输出格式化的数据。您需要做的就是选择/过滤您希望成为手势一部分的指针(基于它们的 ID、屏幕坐标、任何内容……)。之后,MSGesture 对象将为您完成所有魔术。
示例 1:处理长按手势
我们将看到如何长按一个元素(一个包含背景图片的简单 DIV)。一旦元素被长按,我们将添加一些角来指示用户当前已选择该元素。角将通过动态创建 4 个 div 来生成,添加到图像每个角的顶部。最后,一些 CSS 技巧将巧妙地使用变换和线性渐变来实现类似这样的效果
顺序将是
- - 在您感兴趣的 HTML 元素上注册 MSPointerDown 和 MSPointerHold 事件
- 创建一个将目标定位到同一个 HTML 元素 MSGesture 对象
- 在 MSPointerDown 处理程序中,将您想要监视的各种 PointerID 添加到 MSGesture 对象(所有 ID 或子集,取决于您想实现的目标)
- 在 MSPointerHold 事件处理程序中,检查细节,看用户是否刚开始长按手势(MSGES TURE_FLAG_BEGIN 标志)。如果是,则添加角。如果不是,则删除角。
这导致了以下代码
<!DOCTYPE html> <html> <head> <title>Touch article sample 5: simple gesture handler</title> <link rel="stylesheet" type="text/css" href="toucharticle.css" /> <script src="Corners.js"></script> </head> <body> <div id="myGreatPicture" class="container" /> <script> var myGreatPic = document.getElementById("myGreatPicture"); // Creating a new MSGesture that will monitor the myGreatPic DOM Element var myGreatPicAssociatedGesture = new MSGesture(); myGreatPicAssociatedGesture.target = myGreatPic; // You need to first register to MSPointerDown to be able to // have access to more complex Gesture events myGreatPic.addEventListener("MSPointerDown", pointerdown, false); myGreatPic.addEventListener("MSGestureHold", holded, false); // Once pointer down raised, we're sending all pointers to the MSGesture object function pointerdown(event) { myGreatPicAssociatedGesture.addPointer(event.pointerId); } // This event will be triggered by the MSGesture object // based on the pointers provided during the MSPointerDown event function holded(event) { // The gesture begins, we're adding the corners if (event.detail === event.MSGESTURE_FLAG_BEGIN) { Corners.append(myGreatPic); } else { // The user has released his finger, the gesture ends // We're removing the corners Corners.remove(myGreatPic); } } // To avoid having the equivalent of the contextual // "right click" menu being displayed on the MSPointerUp event, // we're preventing the default behavior myGreatPic.addEventListener("contextmenu", function (e) { e.preventDefault(); // Disables system menu }, false); </script> </body> </html>
尝试仅点击或鼠标点击该元素,什么也不会发生。触摸并按住图像上的一个手指,或对其进行长鼠标点击,角会出现。释放您的手指,角会消失。
触摸并按住图像上的 2 个或更多手指,什么也不会发生,因为只有当 1 个独特的手指按住元素时,Hold 手势才会被触发。
注意:白色边框、角和背景图像是通过 toucharticle.css 中定义的 CSS 设置的。Corners.js 只会创建 4 个 DIV(使用 append 函数),并将它们放置在主元素的每个角落的顶部,并带有适当的 CSS 类。
不过,我对当前结果还有些不满意。一旦您长按图片,只要您稍微移动手指,就会触发 MSGESTURE_FLAG_CANCEL
标志并被处理程序捕获,该处理程序会删除角。我更希望只在用户在图片上方释放手指时,或者一旦他的手指移出图片定义的框时,才删除角。要做到这一点,我们将只在 MSPointerUp 或 MSPointerOut 时删除角。这给出了如下代码
var myGreatPic = document.getElementById("myGreatPicture"); // Creating a new MSGesture that will monitor the myGreatPic DOM Element var myGreatPicAssociatedGesture = new MSGesture(); myGreatPicAssociatedGesture.target = myGreatPic; // You need to first register to MSPointerDown to be able to // have access to more complex Gesture events myGreatPic.addEventListener("MSPointerDown", pointerdown, false); myGreatPic.addEventListener("MSGestureHold", holded, false); myGreatPic.addEventListener("MSPointerUp", removecorners, false); myGreatPic.addEventListener("MSPointerOut", removecorners, false); // Once touched, we're sending all pointers to the MSGesture object function pointerdown(event) { myGreatPicAssociatedGesture.addPointer(event.pointerId); } // This event will be triggered by the MSGesture object // based on the pointers provided during the MSPointerDown event function holded(event) { // The gesture begins, we're adding the corners if (event.detail === event.MSGESTURE_FLAG_BEGIN) { Corners.append(myGreatPic); } } // We're removing the corners on pointer Up or Out function removecorners(event) { Corners.remove(myGreatPic); } // To avoid having the equivalent of the contextual // "right click" menu being displayed on the MSPointerUp event, // we're preventing the default behavior myGreatPic.addEventListener("contextmenu", function (e) { e.preventDefault(); // Disables system menu }, false);
示例 2:处理缩放、平移和旋转
最后,如果您想缩放、平移或旋转元素,您只需要编写很少几行代码。您需要首先注册 MSGestureChange 事件。此事件将通过 MSGestureEvent 对象文档中描述的各种属性发送给您,例如当前应用于您 HTML 元素的 rotation(旋转)、scale(缩放)、translationX(X 轴平移)、translationY(Y 轴平移)。
更好的是,默认情况下,MSGesture 对象免费提供惯性算法。这意味着您可以拿起 HTML 元素,用手指在屏幕上滑动它,动画将由我们处理。
最后,为了反映 MSGesture 发送的这些更改,您需要相应地移动元素。最简单的方法是应用一些 CSS 变换,映射与您的手指手势匹配的旋转、缩放、平移详细信息。为此,请使用 MSCSSMatrix 元素。
总之,如果您想在之前的示例中处理所有这些酷手势,请像这样注册事件
myGreatPic.addEventListener("MSGestureChange", manipulateElement, false);
并使用以下处理程序
function manipulateElement(e) { // Uncomment the following code if you want to disable the built-in inertia // provided by dynamic gesture recognition // if (e.detail == e.MSGESTURE_FLAG_INERTIA) // return; // Get the latest CSS transform on the element var m = new MSCSSMatrix(e.target.currentStyle.transform); e.target.style.transform = m .translate(e.offsetX, e.offsetY) // Move the transform origin under the center of the gesture .rotate(e.rotation * 180 / Math.PI) // Apply Rotation .scale(e.scale) // Apply Scale .translate(e.translationX, e.translationY) // Apply Translation .translate(-e.offsetX, -e.offsetY); // Move the transform origin back }
尝试在黑色区域内用 1 个或多个手指移动和滑动图像。也尝试用 2 个或更多手指缩放或旋转元素。结果非常棒,代码也非常简单,因为所有复杂性都已由 IE10 原生处理。
您还可以在这里查看所有这些
- 简单触摸默认示例(未进行任何操作)
- 简单触摸示例步骤 1(使用 CSS -ms-touch-action)
- 简单触摸示例步骤 2a(使用基本 MSPointerMove 实现)
- 简单触摸示例步骤 2b(使用 pointerType 区分)
- 简单触摸示例步骤 3(使用 MSPointers 和鼠标回退)
- MSGesture 示例 1:MSGestureHold 处理程序
- MSGesture 示例 1b:MSGestureHold 处理程序
- MSGesture 示例 2:MSGestureChange
相关资源
- W3C Pointer Events 规范
- 处理所有浏览器中的多点触摸和鼠标输入:这个 polyfill 库应该能帮助很多开发者
- 指针和手势事件
- 使用手势事件超越平移、缩放和点击
- IE Test Drive 浏览器表面,它极大地启发了许多嵌入式演示
- 在 IE10 中尝试一些超棒的触摸游戏
- Contre Jour 并阅读一篇非常有趣的幕后花絮文章
- Atari Arcade Games 并阅读这篇信息量极大的文章:使用 CreateJS 构建 Atari,其中详细介绍了支持所有平台触摸的决策。
- BUILD 会议 3-140 的录像:触摸屏、触控笔和鼠标,哦我的!
从逻辑上讲,有了本文中分享的所有详细信息以及指向其他资源的链接,您现在就可以在您的网站和 Windows 应用商店应用中实现 MSPointer Events 模型了。这样您就有机会轻松地增强 Internet Explorer 10 中用户的体验。
David Rousset 是微软的开发者布道师,专注于 HTML5 和 Web 开发。您可以在 MSDN 上阅读他的博客,或者在 Twitter 上关注他:@davrous。
本文是 Internet Explorer 团队 HTML5 技术系列的一部分。通过 http://modern.IE 提供的 3 个月免费 BrowserStack 跨浏览器测试,试用本文中的概念。