EtchMark 고급 설정: 터치, 마우스, 펜, 장치 흔들기 등을 지원하는 웹 사이트 구축

EtchMark는 전형적인 매직스크린 그림 완구를 새롭게 탄생시킨 제품으로 포인터 이벤트장치 방향을 포함해 IE11의 터치와 최근에 부상하고 있는 웹 표준 지원 개선을 잘 보여 줍니다. 이 글에서는 자체 사이트에 쉽게 추가하여 터치, 마우스, 펜, 키보드 중 어느 것을 사용하더라도 부드럽고 자연스러운 느낌을 주고 장치 흔들기에도 반응하는 환경을 구축할 수 있는 여러 가지 기능에 대해 자세히 소개합니다.

데모의 구조

EtchMark를 사용하면 터치, 마우스, 펜 또는 화살표 키를 사용하여 원하는 것은 무엇이든 화면에 그릴 수 있습니다.  그림을 그리는 표면은 노브가 돌아갈 때마다 업데이트되는 HTML5 캔버스 요소입니다.  벤치마크 모드에서는 requestAnimationFrame API를 사용하는 데 초당 60프레임의 부드러운 애니메이션 루프와 배터리 사용 시간 연장 효과를 제공합니다.  노브의 그림자는 SVG 필터를 사용하여 만듭니다. IE11의 하드웨어 가속을 통해 이 작업의 대부분을 GPU로 옮겨, 놀랍도록 빠른 환경을 만듭니다.  아래 비디오에서 이 기능들의 실제 활용을 살펴본 후, 본격적으로 어떻게 설계되어 있는지 살펴보겠습니다.

EtchMark가 HTML5 캔버스, requestAnimationFrame, SVG 필터, 포인터 이벤트, 장치 방향 API를 사용하여 전형적인 완구를 새롭게 탄생시킴

포인터 이벤트를 사용하는 터치, 마우스, 키보드, 펜

포인터 이벤트를 사용하면 하나의 API에 대한 코딩만으로 마우스, 키보드, 펜, 터치를 지원하는 환경을 구축할 수 있습니다. 포인터 이벤트는 모든 Windows 장치에서 지원되고 다른 브라우저에도 곧 지원될 예정입니다.  이제 포인터 이벤트 사양은 W3C의 권고 후보안이므로 IE11은 접두어가 없는 표준 버전을 지원합니다.

시작하려면 처음 해야 할 일은 Knob.js에서 포인터 이벤트를 연결하는 것입니다. 먼저 접두어가 없는 표준 버전이 있는지 검사하고, 검사에 실패하면 IE10 지원을 가능하도록 하는 데 필요한 접두어가 있는 버전으로 대체합니다.  아래의 예에서는 hitTarget이 노브 이미지가 들어있는 단순 div이며 크기가 약간 더 커서 사용자가 손가락을 대기 편하도록 공간이 확보됩니다. 

    if (navigator.pointerEnabled)

    {

        this.hitTarget.addEventListener("pointerdown", pointerDown.bind(this));

        this.hitTarget.addEventListener("pointerup", pointerUp.bind(this));

        this.hitTarget.addEventListener("pointercancel", pointerCancel.bind(this));

        this.hitTarget.addEventListener("pointermove", pointerMove.bind(this));

    }

    else if (navigator.msPointerEnabled)

    {

        this.hitTarget.addEventListener("MSPointerDown", pointerDown.bind(this));

        this.hitTarget.addEventListener("MSPointerUp", pointerUp.bind(this));

        this.hitTarget.addEventListener("MSPointerCancel", pointerCancel.bind(this));

        this.hitTarget.addEventListener("MSPointerMove", pointerMove.bind(this));

    }

비슷하게, setPointerCapture의 올바른 대안을 Element.prototype에 추가하여 IE와 비슷하게 구현되도록 합니다.

    Element.prototype.setPointerCapture = Element.prototype.setPointerCapture || Element.prototype.msSetPointerCapture;

다음으로 pointerDown 이벤트를 처리해 보겠습니다.  먼저 해야 할 일은 this.hitTarget에서 setPointerCapture를 호출하는 것입니다.  포인터를 캡처하여 이후의 모든 포인터 이벤트가 이 요소로 처리되도록 해야 합니다. 또한 이렇게 하면 포인터가 경계로 이동하더라도 다른 요소들이 이벤트를 발생시키지 않게 됩니다.  이렇게 하지 않으면 사용자의 손가락이 이미지와 포함 div의 경계에 있을 때 문제가 생길 것입니다. 이미지가 포인터 이벤트를 사용할 때도 있고 div가 이를 사용할 때도 있습니다.  그러면 노브가 튀는 들쭉날쭉한 환경이 될 것입니다. 포인터를 캡처하면 이 문제를 간편하게 해결할 수 있습니다.

포인터 캡처는 사용자가 손가락을 노브 위에 올려놓은 다음 서서히 이동하여 대상을 누르면서 계속 회전할 때도 유용합니다.  대상에서 몇 인치 떨어질 때까지 손가락을 떼지 않아도 회전이 부드럽고 자연스럽게 느껴집니다.

마지막으로 setPointerCapture에 대해 유의할 점은 이벤트의 pointerId 속성을 전달한다는 것입니다.  이렇게 하면 여러 개의 포인터를 지원할 수 있어, 사용자는 다른 노브의 이벤트에 방해가 되지 않으면서도 손가락 하나씩으로 동시에 각 노브를 조작할 수 있습니다. 여러 개의 노브를 지원하기 때문에 사용자가 두 노브를 한 번에 회전하여 가로, 세로 선만 그릴 수 있는 것이 아니라 자유 형식 그림을 그릴 수 있습니다.

또한 노브 개체를 가리키는 this에 플래그를 두 개 설정해야 합니다(플래그는 노브별로).

  • pointerEventInProgress - 포인터를 놓았는지 표시
  • firstContact - 사용자가 손가락 놓았는지 표시

    function pointerDown(evt)

    {

        this.hitTarget.setPointerCapture(evt.pointerId);

        this.pointerEventInProgress = true;

        this.firstContact = true;

    }

마지막으로 사용자가 손가락(또는 마우스/펜)을 뗄 때 pointerEventInProgress를 초기화해야 합니다.

    function pointerUp(evt)

    {

        this.pointerEventInProgress = false;

    }

 

    function pointerCancel(evt)

    {

        this.pointerEventInProgress = false;

    }

PointerCancel은 두 가지 방식으로 이루어질 수 있습니다. 첫 번째 방식은 시스템에서 하드웨어 이벤트 등으로 인해 포인터가 이벤트를 계속 생성할 가능성이 낮음을 파악한 경우입니다. 이동 또는 확대/축소의 경우처럼 pointerDown 이벤트가 이미 이루어졌고 포인터를 사용하여 페이지 뷰포트를 조작하는 경우에도 이벤트가 발생합니다.  완벽하게 하기 위해서는 반드시 pointerUp과 pointerCancel 모두 구현하는 것이 좋습니다.

위, 아래, 취소 이벤트가 연결된 상태에서는 pointerMove 지원을 구현할 수 있습니다.  사용자가 처음 손가락을 놓았을 때 심하게 회전하지 않도록 firstContact 플래그를 사용합니다. firstContact를 지운 후에는 손가락의 이동 차이를 계산하면 됩니다.  삼각법을 사용하여 시작과 끝 좌표를 회전 각도로 만든 다음, 그림 기능으로 진행합니다.

    function pointerMove(evt)

    {

        //centerX and centerY are the centers of the hit target (div containing the knob)

        evt.x -= this.centerX;

        evt.y -= this.centerY;

 

        if (this.pointerEventInProgress)

        {

            //Trigonometry calculations to figure out rotation angle

 

            var startXDiff = this.pointerEventInitialX - this.centerX;

            var startYDiff = this.pointerEventInitialY - this.centerY;

 

            var endXDiff = evt.x - this.centerX;

            var endYDiff = evt.y - this.centerY;

 

            var s1 = startYDiff / startXDiff;

            var s2 = endYDiff / endXDiff;

 

            var smoothnessFactor = 2;

            var rotationAngle = -Math.atan((s1 - s2) / (1 + s1 * s2)) / smoothnessFactor;

 

            if (!isNaN(rotationAngle) && rotationAngle !== 0 && !this.firstContact)

            {

                //it’s a real rotation value, so rotate the knob and draw to the screen

                this.doRotate({ rotation: rotationAngle, nonGesture: true });

            }

 

            //current x and y values become initial x and y values for the next event

            this.pointerEventInitialX = evt.x;

            this.pointerEventInitialY = evt.y;

            this.firstContact = false;

        }

    }

단순 이벤트 처리기를 구현하면 자연스럽고 손가락에 딱 붙는 듯한 느낌을 주는 터치 환경이 만들어집니다.  여러 가지의 포인터를 지원하고 사용자가 양쪽 노브를 동시에 조작하여 자유 형식 그림을 그릴 수 있도록 합니다.  가장 좋은 점은 포인터 이벤트를 사용하기 때문에 마우스, 펜, 키보드에 같은 코드를 사용할 수 있다는 것입니다.

게임에 더 많은 손가락을 사용할 수 있는 제스처 지원 추가

사용자가 한 손가락으로 노브를 회전할 경우에는 위에 설명한 포인터 이벤트 코드가 좋지만 사용자가 두 손가락을 사용해 회전시키는 경우는 어떨까요?  삼각법을 사용하여 회전 각도를 계산하고, 두 번째 손가락을 사용하는 경우 적절한 각도 계산이 더 복잡해집니다.  직접 복잡한 코드를 작성하기보다는 IE11의 MSGesture 지원을 활용할 수 있습니다.

    if (window.MSGesture)

    {

        var gesture = new MSGesture();

        gesture.target = this.hitTarget;

 

        this.hitTarget.addEventListener("MSGestureChange", handleGesture.bind(this));

        this.hitTarget.addEventListener("MSPointerDown", function (evt)

        {

            // adds the current mouse, pen, or touch contact for gesture recognition

            gesture.addPointer(evt.pointerId);

        });

    }

이제 이벤트가 연결된 상태에서 제스처 이벤트를 처리할 수 있습니다.

    function handleGesture(evt)

    {

        if (evt.rotation !== 0)

        {

            //evt.nonGesture is a flag we defined in the pointerMove method above.

            //It will be true when we’re handling a pointer event, and false when

            //we’re handling an MSGestureChange event

            if (!evt.nonGesture)

            {

                //set to false if we got here via Gesture so flag is in correct state

                this.pointerEventInProgress = false;

            }

 

            var angleInDegrees = evt.rotation * 180 / Math.PI;

 

            //rotate the knob visually

            this.rotate(angleInDegrees);

 

            //draw based on how much we rotated

            this.imageSketcher.draw(this.elementName, angleInDegrees);

        }

    }

앞서 보셨듯이 MSGesture는 라디안 단위의 각도를 나타내는 단순 회전 속성이므로 수동으로 계산을 다 할 필요가 없습니다.  이제 두 손가락 회전을 지원하여 자연스럽고 손가락에 딱 붙는 느낌을 줍니다.

흔들기를 추가한 장치 동작

IE11은 장치의 물리적 방향과 동작에 대한 정보를 액세스할 수 있는 W3C DeviceOrientation 이벤트 사양을 지원합니다.  장치가 이동하거나 회전(보다 정확하게는 가속)한 경우에는 devicemotion 이벤트가 창에서 실행되고 X, Y, Z축의 가속도(장치의 중력 가속도가 있는 경우와 없는 경우 모두 포함, 단위: m/s2)의 변화량을 제공합니다.  또한 알파, 베타, 감마 회전 각도 변화량을 도/초 단위로 표시합니다.

이 경우, 사용자가 장치를 흔들 때마다 화면을 지워야 합니다.  그렇게 하기 위해서 처음 해야 할 일은 devicemotion 이벤트(이 경우 jQuery 사용)를 연결하는 것입니다.

    $(window).on("devicemotion", detectShaking);

다음으로 사용자가 임계값을 초과하는 가속 속도로 어느 방향으로든 장치를 이동했음을 감지할 수 있습니다.  흔들기를 감지해야 하기 때문에 위와 같은 동작이 두 번 연속 이루어짐을 확인하기 위한 카운터를 마련했습니다.  두 번의 연속 동작을 감지하면 화면을 지웁니다.

    var nAccelerationsInARow = 0;

 

    var detectShaking = function (evt)

    {

        var accl = evt.originalEvent.acceleration;

 

        var threshold = 6;

        if (accl.x > threshold || accl.y > threshold || accl.z > threshold)

        {

            nAccelerationsInARow++;

            if (nAccelerationsInARow > 1)

            {

                eraseScreen();

                nAccelerationsInARow = 0;

            }

        }

        else

        {

            nAccelerationsInARow = 0;

        }

    }

장치 방향에 대한 자세한 내용은 IE 블로그의 이 글을 참조하십시오.

방향 잠금 기능

IE11은 화면 방향 API 그리고 방향 잠금 기능과 같은 기능을 지원합니다.  EtchMark가 성능 벤치마크이기 때문에 화면 해상도와 무관하게 캔버스 크기를 똑같게 하여 모든 장치에서의 작업량을 같게 해야 합니다.  이렇게 하면 작은 화면, 특히 세로 모드에서 작업 공간에 여유가 없게 됩니다.  최고의 경험을 제공하려면 방향을 세로로 잠금 기능을 사용하면 됩니다.

    window.screen.setOrientationLock("landscape");

이렇게 하면 사용자가 장치를 어느 방향으로 회전하든지 화면은 항상 가로 모드로 표시됩니다.  screen.unlockOrientation을 사용하여 방향 잠금 기능을 제거할 수도 있습니다.

전망

포인터 이벤트와 장치 방향 이벤트 같은 상호 운용 가능한 표준 기반 기술을 사용하면 웹 사이트에서 새롭고 흥미로운 경험을 할 수 있습니다. IE11의 뛰어난 터치 지원 환경으로 부드럽고 손가락에 붙는 듯한 느낌과 상호 작용하는 느낌을 받을 수 있습니다. 또한 IE11과 MSGesture를 사용하면 속성에 액세스하는 것처럼 간단히 두 손가락 회전 각도를 계산할 수 있습니다. 이 기술을 본인의 사이트에서 직접 사용해 보시고 의견을 보내 주시기 바랍니다.

- Internet Explorer 프로그램 관리자,
Jon Aneja