快速入門:靜態手勢 (HTML)

[ 本文的目標對象是撰寫 Windows 執行階段 App 的 Windows 8.x 和 Windows Phone 8.x 開發人員。如果您正在開發適用於 Windows 10 的 App,請參閱 最新文件 ]

處理基本 Windows 執行階段手勢事件和自訂 Windows 觸控語言中描述的靜態手勢 (如點選、點兩下、按住不放及右鍵點選) 的使用者體驗。

大部分的應用程式都會處理手勢 (點選、移動瀏覽、縮放等),而且除了將原始指標資料傳送到手勢偵測之外,不常使用這些資料。在這個範例中,我們要使用這個原始指標資料支援靜態手勢的操作和處理,以擴充應用程式的互動模式,並以快速入門:指標中所述的基本指標事件為基礎。

Windows 8.1 的更新: Windows 8.1 對指標輸入 API 引入了數種更新及改進。如需詳細資訊,請參閱 Windows 8.1 的 API 變更

如果您是使用 JavaScript 開發應用程式的新手: 請仔細閱讀這些主題以熟悉這裡討論的技術。

使用 JavaScript 建立您的第一個應用程式

使用 JavaScript 建立應用程式的藍圖

請參閱快速入門:新增 HTML 控制項和處理事件以了解事件

應用程式功能,從開始到完成:

應用程式功能,從開始到完成系列深入探索此功能。

使用者互動,從開始到完成 (HTML)

使用者互動自訂,從開始到完成 (HTML)

使用者經驗指導方針:

平台控制項程式庫 (HTMLXAML) 提供完整的使用者互動體驗,包含標準互動、動畫物理效果及視覺化回饋。 如果您不需要自訂的互動支援,請使用這些內建控制項。

如果平台控制項不足,下列使用者互動指導方針能讓您在各種輸入模式下提供令人讚賞的沈浸式互動體驗。這些指導方針主要著重在觸控輸入,不過與觸控板、滑鼠、鍵盤和手寫筆輸入仍有相關。

範例:應用程式範例中,查看此功能的執行方式。

使用者互動自訂,從開始到完成範例

輸入:DOM 指標事件處理範例

輸入:操作和手勢 (JavaScript) 範例

輸入:Windows 8 手勢範例

目標: 了解如何使用觸控、滑鼠、畫筆/手寫筆互動和 Windows 執行階段手勢事件的輸入,接聽及處理靜態手勢。

先決條件

請參閱快速入門:指標快速入門:DOM 手勢與操作

我們假設您可以利用 JavaScript,以適用於 JavaScript 的 Windows Library 範本建立基本的應用程式。

若要完成這個教學課程,您需要:

完成所需的時間: 30 分鐘.

什麼是手勢事件?

手勢是在輸入裝置上或由輸入裝置 (觸控介面上的單指或多指、畫筆/手寫筆數位板及滑鼠等) 所執行的實際動作。這些自然的互動都會對應到系統和您應用程式中元素的作業。 如需詳細資訊,請參閱手勢、操作以及互動

下表描述本快速入門中涵蓋的靜態手勢。

手勢說明
點選 / 點兩下點選手勢

立即放開或終止的單點接觸。

點選元素會叫用它的主要動作。

點兩下是快速的連續兩次點選,可以依應用程式的要求處理。

  • 進入狀態:在物件的週框矩形內偵測到一個接觸點。
  • 移動:無。
  • 結束狀態:放開或終止接觸點。
按住不放 / 右鍵點選按住不放手勢

在超過時間閾值之前沒有移動的單一接觸點。

按住不放會在不進行任何動作下,顯示詳細的資訊或教學視覺物件 (例如工具提示或操作功能表)。

右鍵點選與按住不放手勢密切關聯。按住不放放開後,就會觸發右鍵點選事件。

  • 進入狀態:在物件的週框矩形內偵測到一個接觸點。
  • 移動:無。
  • 結束狀態:放開或終止接觸點。

如需這些手勢及它們如何與 Windows 觸控語言相關的詳細資訊,請參閱觸控互動設計

 

重要  如果您實作自己的互動支援,請牢記使用者所期待的是與應用程式 UI 元素直接互動的直覺式體驗。 建議您在平台控制項程式庫 (HTMLXAML) 模型化您的自訂互動,以保持一致和可探索的 UI 體驗。這些程式庫中的控制項提供完整的使用者互動體驗,包含標準互動、動畫物理效果、視覺化回饋及協助工具。只有在需求明確且定義清楚,而且沒有基本的互動可以支援您的情況時,才建立自訂互動。

 

建立 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 方法。

秘訣  在此範例中,只有一個與手勢辨識器關聯的物件。如果您的應用程式包含大量可操作的物件 (例如拼圖),請只有在目標物件上偵測到指標輸入時,才考慮動態建立手勢辨識器。操作完成時,即可摧毀手勢辨識器 (如需這類操作的範例,請參閱輸入:可具現化手勢範例)。若要避免建立和摧毀手勢辨識器時產生額外負荷,請在初始化時建立小型的手勢辨識器集區,並視需要動態加以指派。

 

輸入處理器物件包含的手勢辨識器 (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 手勢與操作

快速入門:操作手勢

設計人員

觸控互動設計