빠른 시작: 정적 제스처(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 제스처 샘플

목표: 터치, 마우스, 펜/스타일러스 조작 및 Windows 런타임 제스처 이벤트의 입력을 사용하여 정적 제스처를 수신 대기하고 처리하는 방법을 알아봅니다.

사전 요구 사항

빠른 시작: 포인터빠른 시작: DOM 제스처 및 조작을 참조하세요.

사용자가 JavaScript용 Windows 라이브러리 템플릿을 사용하고 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 속성 msTouchAction, overflow-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 제스처 및 조작

빠른 시작: 조작 제스처

디자이너

터치 조작 디자인