65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2013年3月25日

CPOL

15分钟阅读

viewsIcon

31045

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

30 天开发一个 Windows 8 应用

开发者经常问我:“手机和平板电脑上有这么多触摸设备,我该从哪里开始?”和“构建触摸输入的最简单方法是什么?”简而言之:“这很复杂。”当然,在现代触摸设备浏览器中,或者作为旧浏览器的后备方案,一定有一种更统一的方式来处理 Web 上的多点触摸输入。在本文中,我想向您展示一些使用 MSPointers 进行的浏览器实验——一项新兴的多点触摸技术和 polyfills,可以使跨浏览器支持变得不那么复杂。您可以轻松地在自己的网站上尝试和使用这类代码。

首先,Web 上有很多触摸技术正在发展——为了获得浏览器支持,您需要查看 iOS 触摸事件模型和 W3C 鼠标事件模型,另外还要了解 MSPointers,以支持所有浏览器。然而,标准化正在获得越来越多的支持(和意愿)。9 月份,微软向 W3C 提交了 MSPointers 以便标准化,昨天,我们已经达到了最终征求意见稿:http://www.w3.org/TR/pointereventsMS Open Tech 团队最近还发布了一个 适用于 Webkit 的 Pointer Events 初始原型

我进行 MSPointers 实验的原因并非基于设备份额——而是因为微软处理基本输入的方式与 Web 上现有的方式大不相同,并且值得一看它可能成为什么样子。区别在于,开发人员可以编写更抽象的输入形式,称为“Pointer”(指针)。指针可以是鼠标光标、笔、手指或多个手指在屏幕上的任何接触点。这样您就不必浪费时间为每种输入类型单独编码。

概念

我们将从回顾运行在 Internet Explorer 10 中的应用程序开始,它公开了 MSPointer 事件 API,然后介绍支持所有浏览器的解决方案。之后,我们将看到如何利用 IE10 手势服务,这些服务将帮助您轻松地在 JavaScript 代码中处理触摸。由于 Windows 8 和 Windows Phone 8 共享相同的浏览器引擎,因此这两个平台的代码和概念是相同的。此外,本文中您学到的所有关于触摸的内容都将帮助您在使用 HTML5/JS 构建的 Windows 应用商店应用中执行相同的任务,因为这同样是正在使用的引擎。

MSPointer 的理念是让您通过单一代码库,使用与您已知的经典鼠标事件相匹配的模式来处理鼠标、笔和触摸设备。事实上,鼠标、笔和触摸具有一些共同的属性:您可以用它们移动指针,并且可以用它们点击元素。那么,让我们通过完全相同的代码来处理这些场景!指针将聚合这些共同属性,并以类似于鼠标事件的方式公开它们。

最明显的共同事件是:MSPointerDownMSPointerMoveMSPointerUp,它们直接映射到等效的鼠标事件。您将获得屏幕的 X 和 Y 坐标作为输出。

您还有特定的事件,例如:MSPointerOverMSPointerOutMSPointerHoverMSPointerCancel

当然,也可能存在您希望以不同于默认鼠标行为的方式处理触摸的情况,以便提供不同的用户体验。此外,借助多点触摸屏,您可以轻松地让用户旋转、缩放或平移某些元素。笔/触控笔甚至可以为您提供鼠标无法提供的压力信息。Pointer Events 仍将聚合这些差异,并允许您为每种设备的特定功能构建自定义代码。

注意:当然,如果您拥有 Windows 8/RT 设备上的触摸屏,或者您正在使用 Windows Phone 8,最好在这些设备上测试以下嵌入的示例。不过,您也可以有一些选择

  1. 使用随免费 Visual Studio 2012 Express 开发工具一起提供的 Windows 8 模拟器,获得第一级的体验。有关此工作原理的更多详细信息,请阅读本文:使用 Windows 8 模拟器和 VS 2012 调试 IE10 触摸事件和您的响应式设计
  2. 还可以看看 这个视频,在文章末尾也可以找到其他格式。该 视频演示了下方所有示例 在支持触摸、笔和鼠标的 Windows 8 平板电脑上的效果。
  3. 如果您无法访问 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 11Microsoft 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:支持所有触摸实现

如果您想更进一步,并支持所有浏览器和所有触摸实现,您有两个选择

  1. – 编写代码以并行处理两种事件模型,如本文所述:处理所有浏览器中的多点触摸和鼠标输入
  2. 只需添加对 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 对象的指针,以便它能检测到特定的手势。然后它将触发以下事件之一:MSGestureTapMSGestureHoldMSGestureStartMSGestureChangeMSGestureEndMSInertiaStart。然后,MSGesture 对象将把所有提交的指针作为输入参数,并在它们之上应用一个手势识别器,以输出格式化的数据。您需要做的就是选择/过滤您希望成为手势一部分的指针(基于它们的 ID、屏幕坐标、任何内容……)。之后,MSGesture 对象将为您完成所有魔术。

示例 1:处理长按手势

我们将看到如何长按一个元素(一个包含背景图片的简单 DIV)。一旦元素被长按,我们将添加一些角来指示用户当前已选择该元素。角将通过动态创建 4 个 div 来生成,添加到图像每个角的顶部。最后,一些 CSS 技巧将巧妙地使用变换和线性渐变来实现类似这样的效果

image

顺序将是

  1. - 在您感兴趣的 HTML 元素上注册 MSPointerDown 和 MSPointerHold 事件
  2. 创建一个将目标定位到同一个 HTML 元素 MSGesture 对象
  3. 在 MSPointerDown 处理程序中,将您想要监视的各种 PointerID 添加到 MSGesture 对象(所有 ID 或子集,取决于您想实现的目标)
  4. 在 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 原生处理。

您还可以在这里查看所有这些

相关资源

从逻辑上讲,有了本文中分享的所有详细信息以及指向其他资源的链接,您现在就可以在您的网站和 Windows 应用商店应用中实现 MSPointer Events 模型了。这样您就有机会轻松地增强 Internet Explorer 10 中用户的体验。

David Rousset
David Rousset 是微软的开发者布道师,专注于 HTML5 和 Web 开发。您可以在 MSDN 上阅读他的博客,或者在 Twitter 上关注他:@davrous

本文是 Internet Explorer 团队 HTML5 技术系列的一部分。通过 http://modern.IE 提供的 3 个月免费 BrowserStack 跨浏览器测试,试用本文中的概念。


© . All rights reserved.