快速入门:DOM 手势和操作 (HTML)

[ 本文适用于编写 Windows 运行时应用的 Windows 8.x 和 Windows Phone 8.x 开发人员。如果你要针对 Windows 10 进行开发,请参阅 最新文档 ]

通过基本文档对象模型 (DOM) 手势事件处理,你可以自定义使用 Windows 触摸语言(如滑动、旋转和调整大小)描述的某些基本手势的用户体验。

针对 Windows 8.1 进行的更新: Windows 8.1 针对指针输入 API 引入了多个更新和改善措施。请参阅 Windows 8.1 的 API 更改获取详细信息。

如果你刚开始使用 JavaScript 开发应用: 阅读这些主题来熟悉此处讨论的技术。

采用 JavaScript 创建你的第一个应用

采用 JavaScript 的应用的路线图

通过快速入门:添加 HTML 控件并处理事件来了解事件

应用功能详细信息:

更深入地了解此功能以作为应用功能大全系列的一部分

用户交互详细信息 (HTML)

用户交互自定义详细信息 (HTML)

用户体验指南:

平台控件库(HTMLXAML)提供完整用户交互体验,包括标准交互、动态显示的物理效果和视觉反馈。 如果你不需要自定义的交互支持,请使用这些内置控件。

如果平台控件不够,那么以下用户交互指南可以帮助你提供一种在各种输入模式上保持一致的令人信服的沉浸式交互体验。这些指南主要侧重于触摸输入,但也有与触摸板、鼠标、键盘和触笔输入相关的一些内容。

示例:应用示例中查看正在使用的功能。

用户交互自定义详细示例

HTML 滚动、平移以及缩放示例

输入:DOM 指针和手势处理示例

输入:可实例化手势示例

目标: 了解如何使用来自触摸、鼠标、笔/触笔交互和 DOM 手势事件的输入,侦听、处理和操作基本的平移、旋转和缩放手势。

先决条件

回顾快速入门:指针

我们假设,你可以使用 Windows JavaScript 库模板且采用 JavaScript 创建基本应用。

若要完成此教程,你需要:

完成所需时间: 30 分钟.

手势事件有哪些?

手势是在输入设备(一个或多个手指(在触摸表面上)、笔/触笔数字化器、鼠标等)上或者通过这些设备进行的实际操作或运动。这些自然交互映射到系统和应用上的元素操作。 有关详细信息,请参阅手势、操作和交互

Windows 依赖一套基本手势来与 UI 交互并操纵 UI。

手势说明
点击点击手势

检测到一个接触并立即抬起。

在元素上点击将调用其主要操作。

长按长按手势

检测到一个接触而且不移动。

长按会导致显示详细信息或指导性可视化内容(如工具提示或上下文菜单),并且不允许执行操作。

滑动滑动手势

检测到一个或多个接触,且这些接触向着同一方向移动。

滑动主要用于平移互动,但也可用于移动、绘制或书写。

轻扫轻扫手势

检测到一个或多个接触,且这些接触向着同一方向移动一段很短的距离。

轻扫以选定、进行命令操作和移动

转动转动手势

检测到两个或多个接触且这些接触沿着顺时针或逆时针的弧线旋转。

转动以旋转。

收缩收缩手势

检测到两个或多个接触且这些接触相互靠近。

收缩以缩小。

拉伸拉伸手势

检测到两个或多个接触且这些接触相互远离。

拉伸以放大。

有关这些手势的详细信息以及它们如何与 Windows 触摸语言相关,请参阅触摸交互设计

 

你可以使用手势检测扩展你的应用的交互模型并在快速入门:处理指针输入中所述的基本指针事件的基础上构建。 事实上,你的应用将最有可能消耗手势事件(如处理点击、平移或使用滑块移动,以及使用收缩或拉伸进行缩放),并使用原始指针数据来支持手势检测和处理。

你的应用可以同时处理多个手势(如缩放和旋转),对指针接触进行分组以将某个特定元素作为目标(如将所有接触与初始或主要接触的目标相关联),以及标识某个特定手势或指针接触的目标特定元素。

要点  如果你实现自己的交互支持,请记住,用户期望获得直观的体验,包括直接与应用中的 UI 元素交互。 我们建议你在平台控件库(HTMLXAML)上构建你的自定义交互以保持内容一致和易于发现。这些库中的控件提供完整的用户交互体验,包括标准交互、动态显示的物理效果、视觉反馈和辅助功能。仅当要求清楚、定义良好且基本交互不支持你的方案时才创建自定义交互。

 

创建 UI

对于此示例,我们使用一个矩形 (target) 作为指针输入以及手势检测和处理的目标对象。

该矩形充当一个基本颜色混合器。目标的颜色根据 RGB 颜色选择(红、绿或蓝)及其通过旋转手势报告的目标旋转角度而变化(我们基于旋转角色计算红色、绿色或蓝色值)。

我们在目标对象内为每个指针和手势事件以及应用于目标的当前变换矩阵显示详细信息。

这是此示例的 HTML。

<html>
<head>
    <meta charset="utf-8" />
    <title>PointerInput</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>

    <!-- BasicGesture references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <div class="TargetContainer" id="targetContainer">
        <div id="colorMixer">
            <input type="radio" name="color" value="R" title="Red" id="red" class="Red" /><label for="red" id="labelRed">Red</label>
            <input type="radio" name="color" value="G" title="Green" id="green" class="Green" /><label for="green" id="labelGreen">Green</label>
            <input type="radio" name="color" value="B" title="Blue" id="blue" class="Blue" /><label for="blue" id="labelBlue">Blue</label>
            <div id="targetLog"></div>
            <div id="eventLog"></div>
        </div>
    </div>
</body>
</html>

这是此示例的级联样式表 (CSS)。

注意  在平移或缩放交互期间指针事件不会触发。你可以通过 CSS 属性 msTouchActionoverflow-ms-content-zooming 禁用某个区域上的平移和缩放。

 

body {
    overflow: hidden;
    position: relative;
}

div #targetContainer {
/*
Set the width and height properties of the target container to fill the viewport. 
You can set these properties to 100%, but we use 100vw (viewport width) and 100vh (viewport height).
See https://go.microsoft.com/fwlink/?LinkID=301480 for more detail on CSS units supported by Internet Explorer.
*/
    height: 100vw;
    width: 100vh;
    overflow: hidden;
    position: absolute;
}

div #colorMixer {
/*
A manipulation-blocking element is defined as an element that explicitly 
blocks direct manipulation via declarative markup, and instead fires gesture 
events such as MSGestureStart, MSGestureChange, and MSGestureEnd.
*/
    touch-action: none;
    -ms-transform-origin: 0px 0px;
    position: absolute;
    background-color: black;
    border-color: white;
    border-width: thick;
    border-style: solid;
}

div #colorSelector {
    position: relative;
}

div #eventLog {
    -ms-overflow-style:scrollbar;
}

input.Red {
    background-color: rgb(255,0,0);
}

input.Green {
    background-color: rgb(0,255,0);
}

input.Blue {
    background-color: rgb(0,0,255);
}

侦听指针和手势事件

此代码设置颜色混合器和颜色选择器,并声明各种事件侦听程序。

在大部分情况下,建议你通过选定的语言框架中指针事件处理程序的事件参数获取指针信息。

如果事件参数没有显示你的应用所需的指针详细信息,则可以通过 getCurrentPointgetIntermediatePoints 方法或 currentPointintermediatePoints 属性访问事件参数中的扩展指针数据。我们推荐使用 getCurrentPointgetIntermediatePoints 方法,因为你可以指定指针数据的上下文。

首先,我们声明全局变量,定义一个数据对象 (colorInfo) 来跟踪目标状态,并初始化颜色混合器 (target) 和 RGB 颜色选择器。

var _width = 640;
var _height = 640;

var _pointerInfo;
var _targetLog;

var _selectedColor;
var _colorRed, _colorGreen, _colorBlue;

// Color-specific data object.
//   value: The color value (r, g, or b)
//   rotation: The rotation value used to calculate color value.
//   matrix: The transform matrix of the target.
function colorInfo(value, rotation, matrix) {
    this.value = value;
    this.rotation = rotation;
    this.matrix = matrix;
}

function initialize() {
    // Configure the target.
    setTarget();

    // Initialize color tracking.
    setColors();
}

然后,我们设置颜色混合器,将某个手势识别程序 (msGesture) 与对象相关联,并声明各种事件侦听程序。

提示  对于本例,只有一个与手势识别程序关联的对象。如果你的应用包含大量可操作的对象(例如 jigsaw puzzle),请考虑仅当在目标对象上检测到指针输入时动态地创建一个手势识别程序。该手势识别程序可在操作完成后销毁(有关此示例,请参阅输入:可实例化手势示例)。若要避免创建和销毁手势识别程序的开销,请在初始化时创建一个小的手势识别程序池并在需要时进行动态分配。

 

// Configure the interaction target.
function setTarget() {
    //  Set up the target position, size, and transform.
    colorMixer.style.width = _width + "px";
    colorMixer.style.height = _height + "px";
    colorMixer.style.msTransform = (new MSCSSMatrix()).
        translate((window.innerWidth - parseInt(colorMixer.style.width)) / 2.0,
        (window.innerHeight - parseInt(colorMixer.style.height)) / 2.0);

    // Create gesture recognizer.
    var msGesture = new MSGesture();
    msGesture.target = colorMixer;
    colorMixer.gesture = msGesture;
    // Expando property for handling multiple pointer devices.
    colorMixer.gesture.pointerType = null;

    // Expando property to track pointers.
    colorMixer.pointers = [];

    // Declare event handlers.
    colorMixer.addEventListener("pointerdown", onPointerDown, false);
    colorMixer.addEventListener("pointerup", onPointerUp, false);
    colorMixer.addEventListener("pointercancel", onPointerCancel, false);
    colorMixer.addEventListener("lostpointercapture", onLostPointerCapture, false);
    colorMixer.addEventListener("MSGestureChange", onMSGestureChange, false);
    colorMixer.addEventListener("MSGestureTap", onMSGestureTap, false);
    colorMixer.addEventListener("MSGestureEnd", onMSGestureEnd, false);
    colorMixer.addEventListener("MSGestureHold", onMSGestureHold, false);
}

最后,我们初始化 RGB 颜色选择器(使用事件侦听程序)和 colorInfo 对象。

// Initialize values and event listeners for color tracking.
function setColors() {
    var m = new MSCSSMatrix(colorMixer.style.msTransform);
    _colorRed = new colorInfo(0, 0, m);
    _colorGreen = new colorInfo(0, 0, m);
    _colorBlue = new colorInfo(0, 0, m);

    document.getElementById("red").addEventListener("click", onColorChange, false);
    document.getElementById("green").addEventListener("click", onColorChange, false);
    document.getElementById("blue").addEventListener("click", onColorChange, false);
}

// Re-draw target based on transform matrix associated with color selection.
function onColorChange(e) {
    switch (e.target.id) {
        case "red":
            colorMixer.style.msTransform = _colorRed.matrix;
            break;
        case "green":
            colorMixer.style.msTransform = _colorGreen.matrix;
            break;
        case "blue":
            colorMixer.style.msTransform = _colorBlue.matrix;
            break;
    }
    _selectedColor = e.target.id;

    eventLog.innerText = "Color change";
    targetLog.innerText = colorMixer.style.msTransform;
}

处理指针向下事件

发生指针向下事件时,我们获取选定的 RGB 颜色并通过调用 addPointer 方法将指针与手势识别程序相关联。我们跟踪序列和 pointerType 以将指针和手势识别程序重新关联(如果需要)。

如果未选择任何颜色,我们将忽略该指针事件。

// Pointer down handler: Attach the pointer to a gesture object.
function onPointerDown(e) {
    // Do not attach pointer if no color selected.
    if (_selectedColor === undefined)
        return;
    _selectedColor = getSelectedColor();

    // Process pointer.
    if (e.target === this) {
        this.style.borderStyle = "double";
        //  Attach first contact and track device.
        if (this.gesture.pointerType === null) {
            this.gesture.addPointer(e.pointerId);
            this.gesture.pointerType = e.pointerType;
        }
            // Attach subsequent contacts from same device.
        else if (e.pointerType === this.gesture.pointerType) {
            this.gesture.addPointer(e.pointerId);
        }
            // New gesture recognizer for new pointer type.
        else {
            var msGesture = new MSGesture();
            msGesture.target = e.target;
            e.target.gesture = msGesture;
            e.target.gesture.pointerType = e.pointerType;
            e.target.gesture.addPointer(e.pointerId);
        }
    }
    eventLog.innerText = "Pointer down";
}

// Get the current color.
function getSelectedColor() {
    var colorSelection = document.getElementsByName("color");
    for (var i = 0; i < colorSelection.length; i++) {
        if (colorSelection[i].checked)
            return colorSelection[i].id;
    }
}

处理手势事件

在此代码中,我们处理平移(滑动或轻扫)、旋转和缩放(收缩或拉伸)手势。

// Gesture change handler: Process gestures for translation, rotation, and scaling.
// For this example, we don't track pointer movements.
function onMSGestureChange(e) {
    // Get the target associated with the gesture event.
    var elt = e.gestureObject.target;
    // Get the matrix transform for the target.
    var matrix = new MSCSSMatrix(elt.style.msTransform);

    // Process gestures for translation, rotation, and scaling.
    e.target.style.msTransform = matrix.
        translate(e.offsetX, e.offsetY).
        translate(e.translationX, e.translationY).
        rotate(e.rotation * 180 / Math.PI).
        scale(e.scale).
        translate(-e.offsetX, -e.offsetY);

    // Mix the colors based on rotation value.
    switch (_selectedColor) {
        case "red":
            _colorRed.rotation += ((e.rotation * 180 / Math.PI));
            _colorRed.rotation = _colorRed.rotation % 360;
            targetLog.innerText = _colorRed.rotation.toString();
            if (_colorRed.rotation >= 0)
                _colorRed.value = parseInt(Math.abs(_colorRed.rotation) * (256 / 360));
            else
                _colorRed.value = parseInt((360 - Math.abs(_colorRed.rotation)) * (256 / 360));
            document.getElementById("labelRed").innerText = _colorRed.value.toString();
            _colorRed.matrix = matrix;
            break;
        case "green":
            _colorGreen.rotation += ((e.rotation * 180 / Math.PI));
            _colorGreen.rotation = _colorGreen.rotation % 360;
            targetLog.innerText = _colorGreen.rotation.toString();
            if (_colorGreen.rotation >= 0)
                _colorGreen.value = parseInt(Math.abs(_colorGreen.rotation) * (256 / 360));
            else
                _colorGreen.value = parseInt((360 - Math.abs(_colorGreen.rotation)) * (256 / 360));
            document.getElementById("labelGreen").innerText = _colorGreen.value.toString();
            _colorGreen.matrix = matrix;
            break;
        case "blue":
            _colorBlue.rotation += ((e.rotation * 180 / Math.PI));
            _colorBlue.rotation = _colorBlue.rotation % 360;
            if (_colorBlue.rotation >= 0)
                _colorBlue.value = parseInt(Math.abs(_colorBlue.rotation) * (256 / 360));
            else
                _colorBlue.value = parseInt((360 - Math.abs(_colorBlue.rotation)) * (256 / 360));
            document.getElementById("labelBlue").innerText = _colorBlue.value.toString();
            _colorBlue.matrix = matrix;
            break;
    }
    e.target.style.backgroundColor = "rgb(" + _colorRed.value + ", " + _colorGreen.value + ", " + _colorBlue.value + ")";
    targetLog.innerText = e.target.style.msTransform;
    eventLog.innerText = "Gesture change";
}

根据需要处理其他事件

在此示例中,我们仅报告此处所处理的其他事件。一个更加强大的应用程序将会提供其他功能。

// Tap gesture handler: Display event.
// The touch language described in Touch interaction design (https://go.microsoft.com/fwlink/?LinkID=268162),
// specifies that the tap gesture should invoke an elements primary action (such as launching an application 
// or executing a command). 
// The primary action in this sample (color mixing) is performed through the rotation gesture.
function onMSGestureTap(e) {
    eventLog.innerText = "Gesture tap";
}

// Gesture end handler: Display event.
function onMSGestureEnd(e) {
    if (e.target === this) {
        this.style.borderStyle = "solid";
    }
    eventLog.innerText = "Gesture end";
}

// Hold gesture handler: Display event.
function onMSGestureHold(e) {
    eventLog.innerText = "Gesture hold";
}

// Pointer up handler: Display event.
function onPointerUp(e) {
    eventLog.innerText = "Pointer up";
}

// Pointer cancel handler: Display event.
function onPointerCancel(e) {
    eventLog.innerText = "Pointer canceled";
}

// Pointer capture lost handler: Display event.
function onLostPointerCapture(e) {
    eventLog.innerText = "Pointer capture lost";
}

请参阅此页面底部的相关主题,获取到更复杂的示例的链接。

完整示例

请参阅 DOM 手势和操作完整代码

摘要和后续步骤

在本快速入门中,你学习了在使用 JavaScript 的 Windows 应用商店应用中处理基本手势事件。

基本手势识别与指针事件相结合,对于管理简单的交互(如平移(滑动或轻扫)、旋转和缩放(收缩或拉伸))很有用。

要处理更加精巧的交互并提供完全自定义的用户交互体验,请参阅快速入门:静态手势快速入门:操作手势

有关 Windows 8 触摸语言的详细信息,请参阅触摸交互设计

相关主题

开发人员

响应用户交互

开发 Windows 应用商店应用(JavaScript 和 HTML)

快速入门:指针

快速入门:静态手势

快速入门:操作手势

设计器

触摸交互设计