내용으로 건너뛰기
HTML5 Canvas 마스터하기 – 1부

HTML5 Canvas 마스터하기 – 1부

HTML5 Canvas 마스터링에 대한 일련의 블로그 게시물이 시작됩니다.

7min read

먼저 애니메이션에 초점을 맞출 것인데, 애니메이션은 캔버스를 활용하여 웹 애플리케이션에 새로운 상호 작용 유형과 시각화를 가져오는 가장 좋은 방법 중 하나이기 때문입니다. 다음은 1부에서 빌드하고 있는 내용의 미리 보기 입니다.

Canvas 개요

HTML5 Canvas는 웹 애플리케이션에서 사용할 수 있는 즉시 모드 2D 렌더링 API를 제공합니다. 브라우저 플러그인이 없으며 HTML5 Canvas 구성 요소를 지원하는 최신 브라우저만 필요합니다. 그러나 이전 버전의 Internet Explorer(8 이하)를 지원하려고 하더라도 지원을 제공하는 데 사용할 수 있는 몇 가지 폴리필 (FlashCanvas, exCanvas 등)이 있습니다.

캔버스는 즉시 모드 렌더링 엔진이기 때문에 Silverlight 또는 WPF와 같은 유지 모드 시스템에서와 같이 표시되는 모든 개체를 나타내는 개체 그래프를 작성하는 대신 캔버스의 렌더링 컨텍스트와 통신하고 각 프레임에 대해 어떤 위치에서 렌더링할 시각적 기본 형식을 알려줄 것입니다.

Hello Circle

먼저 화면에 원을 렌더링 해 보겠습니다.

필요한 DOM 요소를 간결하게 조작하는 데 도움이 되도록 이 웹 애플리케이션에 대해 jQuery가 참조되었다고 가정합니다. 그러나 jQuery는 캔버스와 상호 작용하기 위해 반드시 필요한 것은 아닙니다. 먼저 DOM부터 시작하겠습니다. DOM 계층 구조의 어딘가에 캔버스 콘텐츠를 표시하려는 캔버스 요소를 추가해야 합니다. 이 경우 내 페이지 본문의 유일한 요소입니다.

<canvas id="canvas"></canvas>

나중에 내용을 렌더링하려고 할 때 이 요소를 쉽게 찾을 수 있도록 id를 제공했습니다.

이제 원을 캔버스에 렌더링 할 차례입니다. 캔버스에서 2D 렌더링 컨텍스트를 가져오는 것부터 시작해야 합니다. 렌더링 컨텍스트는 프리미티브를 캔버스에 렌더링하는 데 필요한 API를 표시하는 것입니다.

$(function () {
    var canv, context;
    
    $("#canvas").attr("width", "500px").attr("height", "500px");
    canv = $("#canvas")[0];
    context = canv.getContext("2d");

여기에서는 jQuery를 사용하여 id로 캔버스를 찾은 다음 너비와 높이를 설정합니다. 처음에 혼란스러울 수 있는 것은 캔버스와 관련된 두 개의 너비와 높이가 실제로 있다는 것입니다. canvas 요소에는 canvas 내에서 콘텐츠를 렌더링하는 데 사용되는 비트맵의 크기를 나타내는 width 및 height에 대한 특성이 있습니다. 한편, 캔버스에서 css 너비와 높이를 설정할 수도 있습니다. 이는 캔버스에 의해 생성된 비트맵이 페이지에 표시될 때 크기가 조정되는 크기를 나타냅니다. 예를 들어 50x50픽셀 캔버스를 만들고 500x500픽셀로 크기를 조정하면 500픽셀의 정사각형을 늘려 500픽셀의 정사각형 영역을 채울 수 있으므로 매우 뭉툭하게 보일 것입니다. 대부분의 경우 css 크기가 캔버스의 속성 크기와 같기를 원할 것입니다.

다음으로 jQuery에서 canvas DOM 요소에 대한 참조를 추출하고 getContext("2d")를 호출하여 렌더링 컨텍스트를 가져옵니다. 왜 getContext("2d")인가? HTML5 캔버스는 2D API를 넘어 WebGL에서도 필수적인 역할을 하기 때문이지만 지금은 2D에 대해 이야기하고 있습니다.

다음으로 실제 원을 렌더링합니다. 여기서 저는 원을 렌더링 할 draw라는 함수를 정의한 다음 실제 렌더링을 수행하기 위해 호출합니다. 내가 왜 이걸 나눴을까? 이것은 믹스에 애니메이션을 추가할 때 분명해집니다.

function draw() {
   context.fillStyle = "rgb(255,0,0)";
   context.beginPath();
   context.arc(150, 150, 120, 0, 2 * Math.PI, false);
   context.fill();
}

draw();

우리가 여기 있다:

  • 캔버스의 채우기 스타일을 빨간색으로 설정합니다. 여기에서 css color 속성이 허용하는 대부분의 구문을 사용할 수 있습니다.
  • 컨텍스트에 새 경로를 시작하도록 지시합니다.
  • 중심이 150px, 반경이 120인 150px, 시계 방향으로 0도에서 360도까지 스윕하는 호를 그립니다(API는 실제로 각도가 아닌 라디안을 원합니다).
  • 우리가 정의한 경로를 채우도록 컨텍스트에 지시하여 원을 채웁니다.

그리고 여기에 결과가 있습니다.

Hello Animation

Now, lets animate our circle in some way.

먼저 이전 버전에서 수정된 로직은 다음과 같습니다.

var canv, context, lastTime, duration, progress, forward = true;
    
$("#canvas").attr("width", "500px").attr("height", "500px");
canv = $("#canvas")[0];
context = canv.getContext("2d");

lastTime = new Date().getTime();

duration = 1000;
progress = 0;

function draw() {
    var elapsed, time, b;
    
    time = new Date().getTime();
    elapsed = time - lastTime;
    lastTime = time;

    if (forward) {
        progress += elapsed / duration;
    } else {
        progress -= elapsed / duration;
    }
    if (progress > 1.0) {
        progress = 1.0;
        forward = false;
    }
    if (progress < 0) {
        progress = 0;
        forward = true;
    }

    b = 255.0 * progress;
    context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)";
    context.beginPath();
    context.arc(150, 150, 120, 0, 2 * Math.PI, false);
    context.fill();
}

window.setInterval(draw, 1000 / 60.0);

그렇다면 위의 차이점은 무엇입니까?

먼저 window.setInterval을 사용하여 draw 메소드를 반복적으로 호출합니다. 우리는 이것을 초당 60 번 (1000 밀리 초 / 60.0)하려고 시도하고 있습니다.

window.setInterval(draw, 1000 / 60.0);

다음으로, 그릴 때마다 마지막으로 그린 프레임 이후 경과된 시간을 확인하고 애니메이션 완료를 향한 진행 상황을 업데이트합니다. 진행이 완료에 도달하면 방향을 바꾸어 다시 0으로 줄입니다. 그런 다음 0이 되면 돌아서 다시 1을 향해 진행합니다. 이렇게 하면 애니메이션이 앞뒤로 계속 반복됩니다.

time = new Date().getTime();
elapsed = time - lastTime;
lastTime = time;

if (forward) {
    progress += elapsed / duration;
} else {
    progress -= elapsed / duration;
}
if (progress > 1.0) {
    progress = 1.0;
    forward = false;
}
if (progress < 0) {
    progress = 0;
    forward = true;
}

다음으로, 진행 상황은 타원을 렌더링하는 데 사용하는 색상을 구동하는 데 사용됩니다.

b = 255.0 * progress;
context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)";

그래서 우리는 색상으로 움직이는 타원을 가지고 있습니다.

애니메이션 개선

애니메이션이 수행되는 방식을 개선하기 위해 위의 내용을 약간 조정할 수 있습니다. 위의 논리가 반드시 사실은 아닌 두 가지 가정이 있습니다. 로직을 실행하는 머신이 60fps 애니메이션을 제공할 수 있을 만큼 충분히 빠르고, draw 메소드가 부드러운 애니메이션 효과를 생성할 수 있을 만큼 충분히 신뢰할 수 있는 타이밍으로 호출된다고 가정합니다. 두 가정 모두 반드시 사실인 것은 아닙니다. 기계가 너무 느려서 초당 필요한 횟수만큼 애니메이션 업데이트를 수행 할 수 없으며 특히 많은 논리가 자바 스크립트 이벤트 대기열을 망치는 경우 setInterval 은 전혀 안정적으로 호출되지 않습니다. 일부 최신 브라우저는 애니메이션을 수행할 때 이보다 더 나은 작업을 수행할 수 있는 API를 지원합니다. 기본적으로 애니메이션 프레임이 준비되었을 때 브라우저가 알려주도록 요청할 수 있으므로 일부 콘텐츠를 렌더링하여 응답할 수 있습니다. 이렇게 하면 더 안정적이고 부드러운 애니메이션이 제공되며 시스템에서 처리할 수 있는 것보다 더 많은 프레임을 애니메이션화하지 않아도 됩니다. 다음은 requestAnimationFrame API(있는 경우)를 사용하기 위해 폴리필을 사용하고, requestAnimationFrame을 지원하지 않는 브라우저를 사용하는 경우 애니메이션을 위한 이전 방법으로 대체하는 로직의 수정된 버전입니다.

function ensureQueueFrame() {
    if (!window.queueFrame) {
        if (window.requestAnimationFrame) {
            window.queueFrame = window.requestAnimationFrame;
        } else if (window.webkitRequestAnimationFrame) {
            window.queueFrame = window.webkitRequestAnimationFrame;
        } else if (window.mozRequestAnimationFrame) {
            window.queueFrame = window.mozRequestAnimationFrame;
        } else {
            window.queueFrame = function (callback) {
                window.setTimeout(1000.0 / 60.0, callback);
            };
        }
    }
}

$(function () {
    var canv, context, lastTime, duration, progress, forward = true;
    ensureQueueFrame();

    $("#canvas").attr("width", "500px").attr("height", "500px");
    canv = $("#canvas")[0];
    context = canv.getContext("2d");

    lastTime = new Date().getTime();

    duration = 1000;
    progress = 0;

    function draw() {
        var ellapsed, time, b;
        queueFrame(draw);

        time = new Date().getTime();
        ellapsed = time - lastTime;
        lastTime = time;

        if (forward) {
            progress += ellapsed / duration;
        } else {
            progress -= ellapsed / duration;
        }
        if (progress > 1.0) {
            progress = 1.0;
            forward = false;
        }
        if (progress < 0) {
            progress = 0;
            forward = true;
        }

        b = 255.0 * progress;
        context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)";
        context.beginPath();
        context.arc(150, 150, 120, 0, 2 * Math.PI, false);
        context.fill();
    }

    queueFrame(draw);
});

다음 시간

다음에는 훨씬 더 야심찬 일을 할 것입니다. 캔버스 위의 마우스 움직임에 반응하는 눈길을 끄는 애니메이션을 만들 것입니다. 또한 2D 렌더링을 사용하여 일부 3D 효과를 시뮬레이션하지만 3D 매트릭스 변환을 시뮬레이션합니다.

데모 요청