快速入门:静态手势 (HTML)

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

处理基本的 Windows 运行时手势事件并为 Windows 触摸语言中描述的静态手势(如点击、双击、长按和右击)自定义用户体验。

大多数应用都会处理手势(点击、平移、缩放等),但对原始指针数据几乎不做操作,除了将其传递给手势检测。在此示例中,我们为了支持静态手势处理和操作而使用此原始指针数据,以便扩展你的应用的交互模型,并在快速入门:指针中所述的基本指针事件的基础上构建。

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

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

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

采用 JavaScript 的应用的路线图

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

应用功能详细信息:

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

用户交互详细信息 (HTML)

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

用户体验指南:

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

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

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

用户交互自定义详细示例

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

输入:操作和手势 (JavaScript) 示例

输入:Windows 8 手势示例

目标: 了解如何使用来自触摸、鼠标、笔/触笔交互的输入,侦听、处理和操作静态手势。

先决条件

回顾快速入门:指针快速入门:DOM 手势和操作

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

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

完成所需时间: 30 分钟.

手势事件有哪些?

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

下表描述了本快速入门中涵盖的静态手势。

手势描述
点击/双击点击手势

立即释放或终止的单个接触。

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

双击是两次连续的快速点击,可以根据应用的需要进行处理。

  • 进入状态:在对象的边框内检测到一个接触。
  • 运动:无
  • 退出状态:释放或终止接触。
长按/右击长按手势

在超过时间阈值之前不移动的单个接触。

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

右击与长按手势密切相关。当松开长按手势时,会引发右击事件。

  • 进入状态:在对象的边框内检测到一个接触。
  • 运动:无
  • 退出状态:释放或终止接触。

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

 

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

 

创建 UI

此示例是一个基本的问答应用。方框 (inputBox) 充当用来检测和处理指针输入和静态手势的目标对象。问题、线索和答案都显示在该对象中。

该应用提供下面的用户交互功能:

  • 双击:启动和停止问题和应用计时器。
  • 点击:在各个问题之间循环。
  • 长按:显示当前问题的一组线索,在保持接触状态时,每隔几秒钟显示一个新线索。此交互行为遵循视觉反馈指南和触摸语言建议(即,声明将长按手势限制在信息 UI 的显示)。
  • 右击(或松开长按):当抬起接触时,显示一个弹出窗口,询问用户是否要回答问题。而且,我们还遵循视觉反馈指南和 Windows 触摸语言中的上下文菜单建议。注意  为了将重点放在手势处理代码上,从 XML 文件读取问题和答案数据这一功能以及某些 UI 和应用功能并未完全实现。  

这是此示例的 HTML。

<html>
<head>
    <meta charset="utf-8" />
    <title>js</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>
    <script src="/js/inputprocessor.js"></script>
    <script src="/js/datamanager.js"></script>
    <script src="/js/cluemanager.js"></script>
</head>
<body>
    <div class="TargetContainer" id="targetContainer">
        <div id="inputBox">
            <div id="instructions">Tap gray box below: Double tap to start questions, tap for next question, press and hold to show clues.</div>
            <div id="questions">&nbsp;</div>
            <div id="answers">
                <label for="answer">Answer:</label>
                <input type="text" id="answer" maxlength="30" size="30" style="z-index:1" />
                <button id="submit">Submit</button>
                <button id="stumped">Stumped</button>                
            </div>
            <div id="clues">
            </div>
            <div id="timerBox"></div>
        </div>
        <div id="eventLog"></div>

        <div id="answerFloater">
            <p>Show answer?</p>
            <button id="yes">Yes</button>
            <button id="no">No</button>
        </div>
    </div>
</body>
</html>

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

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

 

body {
/*
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.
*/
    overflow: hidden;
    position: absolute;
    font-family: 'Segoe UI';
    font-size: small;
    touch-action: none;
    background-color: black;
}

div #targetContainer {
    position: relative;
    height: fill-available;
    width: fill-available;
}

div #inputBox {
    position: relative;
    width: 640px;
    height: 640px;
    color: black;
    overflow: hidden;
    background-color: darkgrey;
    margin: 0px;
    padding: 0px;
    border-width: 1px;
    border-color: white;
    border-style: solid;
}

div #instructions {
    position: relative;
    width: 100%;
    height: fit-content;
    color: black;
    background-color: white;
    visibility: visible;
}

div #questions {
    position: relative;
    width: 100%;
    height: fit-content;
    color: white;
    background-color: black;
    visibility: visible;
}

div #answers {
    position: relative;
    width: 100%;
    height: fit-content;
    color: white;
    background-color: black;
    visibility: visible;
}

div #clues {
    position: relative;
    width: 100%;
    height: 100%;
    background-color: DimGray;
}

div #timerBox {
    background-color: red;
    color: black;
    position: absolute;
    width: 100%;
    bottom: 0px;
    height: 20px;
    text-align: center;
}

div #answerFloater {
    position: absolute;
    visibility: hidden;
    top: 0px;
    left: 0px;
    background-color: blue;
}

div #eventLog {
    font-size: xx-small;
    position: absolute;
    left: 0px;
    top: 0px;
    width: 640px;
    height: 50px;
    overflow: auto;
    overflow-style: auto;
}

初始化应用

初始化问题和答案对象。

我们将在下面声明全局变量并引用 UI 对象。

var _applicationData;
var _localSettings;
var _data;
var _inputBox;
var _instructions;
var _answers;
var _questions;
var _clues;
var _eventLog;
var _floater;

function initialize() {
    // Get our UI objects.
    _inputBox = document.getElementById("inputBox");
    _instructions = document.getElementById("instructions");
    _questions = document.getElementById("questions");
    _answers = document.getElementById("answers");
    _clues = document.getElementById("clues");
    _eventLog = document.getElementById("eventLog");
    _floater = document.getElementById("answerFloater");

    // Configure the target.
    setTarget();
}

然后,我们将定位问题和答案 UI,并设置用于从 XML 文件处理问题和答案数据的交互对象。此示例的 XML 数据详细信息可以在本主题末尾处的完整列表中查看。

// Configure the interaction target.
function setTarget() {
    //  Set the position of the input target.
    var inputLeft = (window.innerWidth - _inputBox.clientWidth) / 2.0;
    var inputTop = (window.innerHeight - _inputBox.clientHeight) / 2.0;
    var transform = (new MSCSSMatrix()).translate(inputLeft, inputTop);
    _inputBox.style.msTransform = transform;

    // Set the position of the event log.
    transform = (new MSCSSMatrix()).translate(inputLeft, inputTop + _inputBox.clientHeight);
    _eventLog.style.msTransform = transform;

    // Associate interaction target with our input manager.
    // Scope input to clue area only.
    _clues.inputProcessor = new QandA.InputProcessor(_clues);
}

配置手势识别器。

在此处,我们将设置交互处理。

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

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

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

 

输入处理器对象包括手势识别器 (gr),它可侦听和处理所有的指针和手势事件。问题和答案 UI 由手势识别器事件处理程序管理。

// Handle gesture recognition for this sample.
(function () {
    "use strict";
    var InputProcessor = WinJS.Class.define(
    // Constructor
    function InputProcessor_ctor(target) {
        this._questionsStarted = false;
        this._tapCount = 0;
        // Create a clue manager.
        this._clueManager = new QandA.ClueManager();
        // Load xml data from file into local app settings.
        var _dataObject = new QandA.DataManager();
        _data = _dataObject.getData();

        this._questionTotal = _data.selectNodes("questions/question").length;
        this._doubleTap = false;
        this._startTime;
        this._intervalTimerId;

        // Initialize the gesture recognizer.
        this.gr = new Windows.UI.Input.GestureRecognizer();

        // Turn off visual feedback for gestures.
        // Visual feedback for pointer input is still displayed. 
        this.gr.showGestureFeedback = false;

        // Configure gesture recognizer to process the following:
        // double tap               - start questions and timer.
        // tap                      - move to next question.
        // right tap                - show answer.
        // hold and hold with mouse - start clues.
        this.gr.gestureSettings =
            Windows.UI.Input.GestureSettings.tap |
            Windows.UI.Input.GestureSettings.doubleTap |
            Windows.UI.Input.GestureSettings.rightTap |
            Windows.UI.Input.GestureSettings.hold |
            Windows.UI.Input.GestureSettings.holdWithMouse;

        //
        // Set event listeners.
        //
        // Get our context.
        var that = this;

        // Register event listeners for these gestures.
        this.gr.addEventListener('tapped', tappedHandler);
        this.gr.addEventListener("holding", holdingHandler);
        this.gr.addEventListener("righttapped", rightTappedHandler);

        // The following functions are registered to handle DOM pointer events
        //
        // Basic pointer handling to highlight input area.
        target.addEventListener("pointerover", function onPointerOver(eventInfo) {
            eventInfo.stopImmediatePropagation = true;
            _eventLog.innerText += "pointer over || ";
            eventInfo.target.style.backgroundColor = "DarkGray";
        }, false);
        // Basic pointer handling to highlight input area.
        target.addEventListener("pointerout", function onPointerOut(eventInfo) {
            eventInfo.stopImmediatePropagation = true;
            _eventLog.innerText += "pointer out || ";
            eventInfo.target.style.backgroundColor = "DimGray";
        }, false);
        // Handle the pointer move event.
        // The holding gesture is routed through this event.
        // If pointer move is not handled, holding will not fire.
        target.addEventListener("pointermove", function onPointerMove(eventInfo) {
            eventInfo.stopImmediatePropagation = true;
            // Get intermediate PointerPoints
            var pps = eventInfo.intermediatePoints;

            // Pass the array of PointerPoints to the gesture recognizer.
            that.gr.processMoveEvents(pps);
        }, false);
        // Handle the pointer down event.
        target.addEventListener("pointerdown", function onPointerDown(eventInfo) {
            eventInfo.stopImmediatePropagation = true;
            _eventLog.innerText += "pointer down || ";

            // Hide the floater if visible.
            _floater.style.visibility = "hidden";

            // Get the PointerPoint for the pointer event.
            var pp = eventInfo.currentPoint;

            // Get whether this pointer down event is within
            // the time threshold for a double tap.
            that._doubleTap = that.gr.canBeDoubleTap(pp);

            // Pass the PointerPoint to the gesture recognizer.
            that.gr.processDownEvent(pp);
        }, false);
        // Handle the pointer up event.
        target.addEventListener("pointerup", function onPointerUp(eventInfo) {
            eventInfo.stopImmediatePropagation = true;
            _eventLog.innerText += "pointer up || ";

            // Get the current PointerPoint
            var pp = eventInfo.currentPoint;

            // Pass the PointerPoint to the gesture recognizer.
            that.gr.processUpEvent(pp);
        }, false);

        // The following functions are registered to handle gesture events.
        //
        // This handler processes taps and double taps.
        // Potential double taps are identified in the pointer down handler.
        function tappedHandler(evt) {
            // Single tap and questions started: Display next question.
            if (!that._doubleTap && that._questionsStarted) {
                _eventLog.innerText += "tapped || ";
                _instructions.innerText = "Double tap to stop questions.";
                _clues.innerText = "";
                that._tapCount++;
                that._clueManager.tapCount = that.tapCount;
                if (that._tapCount > that._questionTotal) {
                    _questions.innerText = "No more questions.";
                } else {
                    var xpath = "questions/question[" + (that._tapCount % (that._questionTotal + 1)) + "]/q";
                    // Read data from a simple setting
                    _questions.innerText = _data.selectSingleNode(xpath).innerText;
                }
            }
                // Single tap and questions not started: Don't do much.
            else if (!that._doubleTap && !that._questionsStarted) {
                _eventLog.innerText += "tapped || ";
                _instructions.innerText = "Double tap to start questions.";
            }
                // Double tap and questions not started: Display first question.
            else if (that._doubleTap && !that._questionsStarted) {
                _eventLog.innerText += "double-tapped || ";
                // Return if last question displayed.
                if (that._tapCount > that._questionTotal) {
                    _questions.innerText = "No more questions.";
                    return;
                }
                // Start questions.
                that._questionsStarted = true;
                _instructions.innerText = "Starting questions (double tap to stop questions).";

                // Question number is based on tap count.
                that._tapCount++;

                // Select question from XML data object.
                var xpath = "questions/question[" + (that._tapCount % (that._questionTotal + 1)) + "]/q";
                _questions.innerText = _data.selectSingleNode(xpath).innerText;

                // Display a basic timer once questions started.
                that._startTime = new Date().getTime();
                that._intervalTimerId = setInterval(displayTimer, 100);
            }
                // Double tap and questions started: Stop questions and timer.
            else if (that._doubleTap && that._questionsStarted) {
                _eventLog.innerText += "double-tapped || ";
                _instructions.innerText = "Questions stopped (double tap to start questions).";
                that._questionsStarted = false;
                clearInterval(that._intervalTimerId);
            }
        };

        // For this app, we display a basic timer once questions start.
        // In a more robust app, could be used for achievements.
        function displayTimer() {
            var x = new Date().getTime();
            timerBox.innerText = (x - that._startTime) / 1000;
        }

        // This handler processes right taps.
        // For all pointer devices a right tap is fired on
        // the release of a press and hold gesture.
        // For mouse devices, righttapped is also fired on a right button click.
        // For pen devices, 
        function rightTappedHandler(evt) {
            if (!that._questionsStarted) {
                return;
            }
            var transform = (new MSCSSMatrix()).
                translate(
                (window.innerWidth - _inputBox.clientWidth) / 2.0 + evt.position.x,
                (window.innerHeight - _inputBox.clientHeight) / 2.0 + evt.position.y);
            _floater.style.visibility = "visible";
            _floater.style.msTransform = transform;
            eventLog.innerText = "right-tap || ";
        }

        // The pointer move event must also be handled because the 
        // holding gesture is routed through this event.
        // If pointer move is not handled, holding will not fire.
        // A holding event is fired approximately one second after 
        // a pointer down if no subsequent movement is detected.
        function holdingHandler(evt) {
            if (!that._questionsStarted)
                return;
            if (evt.holdingState == Windows.UI.Input.HoldingState.started) {
                _eventLog.innerText += "holding || ";
                // Create a clue manager.
                that._clueManager.tapCount = that._tapCount;
                // Start displaying clues.
                that._clueManager.displayClues();
            } else if (evt.holdingState == Windows.UI.Input.HoldingState.completed) {
                that._clueManager.destroy();
                _eventLog.innerText += "holding completed || ";
            } else {
                _eventLog.innerText += "holding canceled || ";
            }
        }
    },
    {},
    {});

    WinJS.Namespace.define("QandA", {
        InputProcessor: InputProcessor
    });
})();

最后,我们配置线索管理器,在使用长按手势期间,线索管理器基于当前的问题显示一系列线索。

// Handle data for this sample.
(function () {
    "use strict";
    var ClueManager = WinJS.Class.define(
    // Constructor
    function ClueManager_ctor() {
        this._clueTimerId = null;
    },
    {
        displayClues: function () {
            var clue;
            var clueCount = 0;
            var clueCollection = _data.selectNodes("questions/question[" + this.tapCount + "]/clues/clue");

            this._clueTimerId = setInterval(function () {
                clueCount++;

                if (clueCount > clueCollection.length) {
                    _clues.innerText += "\nNo more clues.";
                    clearInterval(_clueTimerId);
                    return;
                }

                if (clueCount == 1)
                    clue = clueCollection.first();

                _clues.innerText += "\n" + clue.current.innerText;
                clue.moveNext();
            }, 2000);
        },
        destroy: function () {
            clearInterval(this._clueTimerId);
        },
        tapCount: {
            get: function () {
                return this._tapCount;
            },
            set: function (tapCount) {
                this._tapCount = tapCount;
            }
        }
    },
    {});

    WinJS.Namespace.define("QandA", {
        ClueManager: ClueManager
    });
})();

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

完整示例

请参阅静态手势完整代码

摘要和后续步骤

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

基本手势识别与指针事件相结合,对于管理简单的交互(如点击、双击、长按和右击)很有用。

有关更复杂的手势处理示例,请参阅输入:可实例化手势示例

注意  此示例不遵循 Windows 触摸语言中有关自定义交互的指南。已经为了说明目的而重新定义了一些静态手势。

 

若要处理更复杂的操作交互(例如滑动、轻扫、转动、收缩和拉伸)以提供完全自定义的用户交互体验,请参阅快速入门:操作手势

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

相关主题

开发人员

响应用户交互

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

快速入门:指针

快速入门:DOM 手势和操作

快速入门:操作手势

设计器

触摸交互设计