canvas

HTML5에 추가된 요소입니다.
이 요소는 페이지 일부를 그래픽 생성 가능한 영역으로 지정하고 자바스크립트로 즉석에서 그림을 그립니다.
현재는 기본적인 그림 기능이 있는 2D 컨텍스트가 있고 WebGL이라는 3D 컨텍스트가 제안된 상태입니다.

WebGL의 경우는 자체가 실험적이기도 하며 윈도 xp같은 오래된 운영체제에는 WebGL 구현에 필요한 그래픽 드라이버가 없는 문제도 존재합니다.

기본사용법

<canvas>요소는 width와 height 속성을 통해 생성할 그림의 크기를 설정합니다.

<canvas id="drawing" width="200" height="200">drawing of something</canvas>

drawing of something 텍스트는 canvas요소가 지원되지 않을때 표시되는 텍스트입니다.

캔버스에 그림을 그리려면 먼저 컨텍스트를 가져와야 합니다.
컨텍스트에 대한 참조는 getContext()메서드에 컨텍스트 이름을 넘깁니다.
2d 컨텍스트 객체를 가져오는 예제를 보겠습니다.

var drawing = document.getElementById("drawing");

//<canvas>가 완전히 지원되는지 확인
if(drawing.getContext){
    var context = drawing.getContext("2d");

    //코드
}

getContext가 있는지 꼭 체크하고 사용하여야 합니다.
특정 브라우저에서는 해당 메서드가 존재하지 않아서 생성되버리는 경우도 있기 때문입니다.

<canvas>에서 생성된 이미지는 toDataURL() 메서드로 내보낼 수 있습니다.
매개변수로는 생성할 이미지의 MIME Type을 받습니다.
아래 예제를 보겠습니다.

var drawing = document.getElementById("drawing");

//<canvas>가 완전히 지원되는지 확인
if(drawing.getContext){
    // 이미지의 데이터 URI
    var imgURI = drawing.toDataURL("image/png");

    //이미지 표시
    var image = document.createElement("img");
    image.src = imgURI;
    document.body.appendChild(image);
}

위 코드는 png형식 이미지를 반환합니다.

2D 컨텍스트

2D 컨텍스트에는 사각형, 원호, 패스 등 단순한 2D그리기용 메서드가 들어있습니다.
좌표는 canvas요소 왼쪽 위에서 시작하며 이 지점을 (0,0)으로 간주합니다.
x는 오른쪽으로, y는 아래쪽으로 픽셀단위로 증가합니다.

채우기와 스트로크

채우기는 도형을 내부를 색깔이나 그레이디언트, 이미지로 채웁니다.
스트로크는 외곽선에 색을 칠합니다.
각각 fillStyle, strokeStyle 프로퍼티로 제어합니다.

var drawing = document.getElementById("drawing");

//canvas가 지원되는지 확인
if(drawing.getContext){
    var context = drawing.getContext("2d");
    context.strokeStyle = "red";
    context.fillStyle = "#0000ff";
}

값은 16진수, rgb, rgba, hsl, hsla의 CSS색깔 형식중 하나를 사용합니다.

사각형 그리기

사각형을 그릴때는 fillRect(), strokeRect(), clearRect()의 메서드를 사용합니다.
모두 매개 변수로 사각형의 x/y좌표, 너비/높이 네가지를 받습니다. (픽셀단위)

fillRect()



var drawing = document.getElementById("drawing");

//canvas가 지원되는지 확인
if(drawing.getContext){
    var context = drawing.getContext("2d");

    //빨간 사각형
    context.fillStyle = "#ff0000";
    context.fillRect(10, 10, 50, 50);

    //반투명 파란색 사각형
    context.fillStyle = "rgba(0,0,255,0.5)";
    context.fillRect(30, 30, 50, 50);
}

strokeRect()



var drawing = document.getElementById("drawing");

//canvas가 지원되는지 확인
if(drawing.getContext){
    var context = drawing.getContext("2d");

    //빨간 사각형
    context.strokeStyle = "#ff0000";
    context.strokeRect(10, 10, 50, 50);

    //반투명 파란색 사각형
    context.strokeStyle = "rgba(0,0,255,0.5)";
    context.strokeRect(30, 30, 50, 50);
}

외곽선만 존재하는 사각형이 생성됩니다.

clearRect()



var drawing = document.getElementById("drawing");

//canvas가 지원되는지 확인
if(drawing.getContext){
    var context = drawing.getContext("2d");

    //빨간 사각형
    context.fillStyle = "#ff0000";
    context.fillRect(10, 10, 50, 50);

    //반투명 파란색 사각형
    context.fillStyle = "rgba(0,0,255,0.5)";
    context.fillRect(30, 30, 50, 50);

    //두 사각형에 모두 겹치는 사각형 영역을 지움
    context.clearRect(40, 40, 10, 10);
}

패스 그리기

패스를 이용해서 복잡한 도형과 선을 그립니다.
첫번째 beginPath()를 호출해서 새 패스를 시작하고 아래 메서드들로 패스를 그립니다.

  • arc(x, y, radius, startAngle, endAngle, counterclockwise)
    x,y를 중심으로 반지름(radius), startAngle에서 시작해서 endAngle로 끝나는 원호를 그립니다.
    Angle은 라디안 단위입니다.
    마지막 매개변수는 startAngle, endAlgle를 시계 반대방향으로 계산 하라는 불리언 입니다.
  • arcTo(x1, y1, x2, y2, radius)
    마지막 지점에서부터 (x2, y2)로 이어지며 (x1, y1)을 지나는, 반지름이 radius인 원호를 그립니다.
  • bezierCurveTo(c1x, c1y, c2x, c2y, x, y)
    마지막 지점부터 (x,y)까지 이어지는 베지어 곡선을 그리며 조절점은 (c1x, c1y), (c2x, c2y)입니다.
  • lineTo(x, y)
    마지막 지점부터 (x, y)까지 직선을 그립니다.
  • moveTo(x, y)
    선을 그리지 않고 커서만 (x, y)로 옮깁니다.
  • quadraticCurveTo(cx, cy, x, y)
    마지막 지점부터 (x, y)까지 사각형 곡선을 그립니다. 조절점은 (cx, cy)입니다.
  • rect(x, y, width, height)
    (x, y)에서 시작하는 사각형을 주어진 너비와 높이로 그립니다.
    별도의 도형을 그리는게 아니라 패스만 생성합니다.(strokeRect(), fillRect()와는 다름)

패스를 생성한 후에 여러가지 일을 할 수 있습니다.
closePat()를 사용하여 패스 시작점으로 돌아갈 수 있습니다. 이후 fillStyle로 채우려면 fill()을 사용합니다. stroke()를써서 외곽선만 그릴수도 있고 clip()메서드를 써서 패스에서 새 클립영역을 생성 할 수도 있습니다.

아래는 시계를 만드는 에제입니다.

var drawing = document.getElementById("drawing");

//canvas가 지원되는지 확인
if(drawing.getContext){
    var context = drawing.getContext("2d");

    //패스 시작
    context.beginPath();

    //바깥쪽 원
    context.arc(100, 100, 99, 0, 2 * Math.PI, false);

    //안쪽원
    context.arc(100, 100, 94, 0, 2 * Math.PI, false);

    //분침
    context.moveTo(100, 100);
    context.lineTo(100, 15);

    //시침
    context.moveTo(100, 100);
    context.lineTo(35, 100);

    //선 표현
    context.stroke();
}

패스를 사용하면 위와같이 그림을 더 세밀히 조절할수 있어서 자주 사용합니다.
x와 y좌표를 매개로 받는 isPointInPath()라는 메서드도 있습니다.
이 메서드는 주어진 지점이 패스에 존재하는지 판단하며 패스를 닫기 전에는 언제든 아래와 같이 호출할 수 있습니다.

if(context.isPointInPath(100, 100)){
    console.log("Point (100, 100) is in the path.);
}

텍스트 그리기

텍스트와 그래픽을 함께 써야 할때가 잦기떄문에 텍스트를 그리는 메서드도 들어있습니다.
fillText(), strokeText()두가지입니다.
x좌표, y좌표, 옵션으로 픽셀 너비의 최대값을 받으며 아래 옵션들을 사용합니다.

  • font : 폰트 스타일과 크기, “10px Arial” 과 같이 사용합니다.
  • textAlign : 텍스트를 어떻게 정렬할지 나타냅니다.
    start, end, left, right, center(left, right대신 start, end를 쓰기 권합니다.)
  • textBaseline : 텍스트의 기준선
    top, hanging, middle, alphabetic, ideographic, bottom

하지만 텍스트는 폰트별로 다르기도 하고 여러가지 문제로 특정 영역안에 맞추기 복잡하므로 도움이 되게 텍스트 크기를 반환하는 measureText()메서드를 제공합니다.
이 메서드는 매개변수로 text를받고 TextMetrics 객체를 반환합니다.
measureText()는 font, textAlign, textBaseline의 현재 값을 바탕으로 텍스트 크기를 계산합니다.

텍스트를 140px너비의 사각혀엥 딱 맞추고 싶을 경우 아래와 같이 할 수 있습니다.

var fontSize = 100;
context.font = fontSize + "px Arial";

while(context.measureText("Hello world!").width > 140){
    fontSize--;
    context.font = fontSize + "px Arial";
}

context.fillText("Hello world!", 10, 10);
context.fillText("Font size is " + fontSize + "px", 10, 50);

텍스트를 그리는건 이런부분때문에 매우 복잡하며 canvas 지원 브라우저도 API 전체를 구현한게 아니니 주의 해야합니다.

변형

컨텍스트를 변형(transformation)하면 캔버스에 그린 이미지도 바뀝니다.
아래와 같은 메서드를 지원합니다.

  • rotate(angle)
    이미지를 원점 중심으로 angle라디안 만큼 회전
  • scale(scaleX, scaleY)
    이미지를 x방향으로 scaleX만큼, y방향으로 scaleY만큼 확대/축소 합니다. 기본값은 1.0
  • translate(x, y)
    원점을 (x, y)로 옮김, (x, y)였던 좌표가 (0,0)이 됩니다.
  • transform(m1_1, m1_2, m2_2, m2_2, dx, dy)
    다음과 같이 변형트릭스를 조작합니다.
    m1_1 m1_2 dx
    m2_2 m2_2 dy
    0 0 1
  • setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy)
    변형 매트릭스를 기본 상태로 리셋한 후 transform()을 호출

변형을 이용하여 시침 분침을 그리는 코드를 봅시다

이전 시계 그리기와 보기에는 동일합니다. 하지만 소스를 보면 translate를 이용해서 다르게 그리는걸 볼 수 있습니다.

var drawing = document.getElementById("drawing");

//canvas가 지원되는지 확인
if(drawing.getContext){
    var context = drawing.getContext("2d");

    //패스 시작
    context.beginPath();

    //바깥쪽 원
    context.arc(100, 100, 99, 0, 2 * Math.PI, false);

    //안쪽원
    context.arc(100, 100, 94, 0, 2 * Math.PI, false);

    //중심으로 원점 이동
    context.translate(100, 100);

    //분침
    context.moveTo(0, 0);
    context.lineTo(0, -85);

    //시침
    context.moveTo(0, 0);
    context.lineTo(-65, 0);

    //선 표현
    context.stroke();
}

translate로 원점을 재설정 후, (0,0)으로 이동하여 분침과 시침을 그려주었습니다.

상태 저장

save(), restore()메서드로 스택에 보관된 설정을 가져와서 현재 상태를 덮어씁니다.
save()로 원하는 만큼 보관할 수 있고 restore()로 한단계씩 올라갈 수 있습니다.

context.fillSTyle = "#ff0000";
context.save();

context.fillStyle = "#00ff00";
context.translate(100, 100);
context.save();

context.fillSTyle = "#0000ff";
context.fillRect(0, 0, 100, 200);

context.restore();
context.fillRect(10, 10, 100, 200);

context.restore();
context.fillRect(0, 0, 100, 200);

save()는 컨텍스트에 적용된 설정과 변형을 저장할 뿐 컨텍스트의 콘텐츠를 저장하지는 않습니다.

이미지그리기

이미지에 대한 내장도 지원합니다.
drawImage()메서드를 사용합니다.

var image = document.images[0];

//(10, 10)위치에 그대로 출력
context.drawImage(image, 10, 10);

//(50, 10)위치에 20x30짜리 이미지로 출력
context.drawImage(image, 50, 10, 20, 30);

//(0, 10)위치에 50x50픽셀이며
//(0, 100)지점에 40x60크기로 그려집니다.
//즉 일부만 그려집니다
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);

HTML <img>요소와 좌표만 넘기면 됩니다.

하지만 다른 소스에서 가져온 이미지를 사용하면 에러가 발생합니다.

그림자

몇가지 프로퍼티에 따라서 자동으로 도형이나 패스에 그림자를 그립니다.

  • shadowColor
    CSS문법으로 표현한 그림자 색깔입니다.
  • shadowOffsetX
    도형이나 패스의 x 좌표에서 그림자의 x좌표가 얼마나 떨어진지에 따른 값입니다.
  • shadowOffsetY
    도형이나 패스의 y 좌표에서 그림자의 y좌표가 얼마나 떨어진지에 따른 값입니다.
  • shadowBlue
    얼마나 흐리게 할지에 대한 값입니다.



context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 0;
context.shadowColor = "rgba(0, 0, 0, 0.5);

context.fillStye = "red";
context.fillRect(10, 10, 50, 50);

그레이디언트

createLinear Gradient()메서드를 호출해서 사용가능합니다.
객체 생성후 addColorStop() 메서드로 색깔 중단점도 설장할 수 있습니다.

중단점은 0에서 1사이의 숫자입니다.

var gradient = context.createLinearGradient(30, 30, 70, 70);

gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");

context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);

위와같이 사용하면 되지만 사각형의 좌표가 변경되거나 할 경우 문제가 있으므로 gradient생성 시 context의 좌표를 추적하게 해주면 유용하게 사용할 수 있습니다.

복합

그림전체에 영향을 미치는 두가지 프로퍼티에 대해서 알아봅시다.

globalAlpha 프로퍼티

투명도를 나타내는 0과 1사이의 숫자를 가진 프로퍼티 입니다.
그림에 투명도를 설정할 수 있습니다.

globalCompositionOperation 프로퍼티

새로 그릴 도형이 이미 컨텍스트에 있는지와 어떻게 합쳐질지를 지정합니다.
아래 값중 하나를 사용합니다.

  • source-over(기본값) : 새그림은 이미 존재하는 이미지 위에 그려집니다.
  • source-in : 새그림은 이미 존재하는 이미지와 겹치는 부분에만 그립니다. (겹치지않으면 투명)
  • source-out : 새그림은 이미 존재하는 이미지와 겹치지 않는 부분만 그립니다. (겹치면 투명)
  • source-atop : 새그림은 이미 존재하는 이미지와 겹치는 부분에만 그립니다. (겹치지않는 부분 영향 X)
  • destination-in : 새그림은 이미 존재하는 이미지 아래 그려집니다. 투명한 픽셀 아래에 있는 부분만 보입니다.
  • destination-over : 새그림은 이미 존재하는 이미지 아래 그려지며 두 미이지가 겹치지 않은곳은 모두 투명해집니다.
  • destination-out : 새그림은 이미 존재하는 이미지의 겹치는 부분을 삭제합니다.
  • destination-atop : 새그림은 이미 존재하는 이미지 아래 그려집니다
  • lighter : 새그림과 이미 존재하는 이미지값을 조합해 더 밝은 이미지를 생성합니다.
  • copy : 새 그림이 이미 존재하는 이미지를 완전히 교체
  • xor : 새 그림의 이미지 데이터와 이미 존재하는 이미지데이터를 xor로 연산하여 표현

이들은 아래 상세한 예제를 보고 쉽게 확인할 수 있습니다.


W3Schools 예제코드 바로가기

하지만 브라우저마다 다르게 구현되어있으므로 주의해야 합니다.

그외

패턴과 이미지 데이터다루기 위한 아래와 같은 메서드도 있습니다.

패턴

이미지를 패턴화시켜서 채우거나 외곽선을 그릴수 있습니다.
패턴 바로가기

이미지데이터 다루기

필터를 적용하거나 할 수 있습니다.
이미지데이터 다루기 바로가기

마치며

캔버스를 어떻게 다루는지 간단하게 살펴보았습니다.
애니메이션이나 기타 기능유용한 기능들도 많지만 이 포스트에서는 간단하게만 알아보았습니다.
자세한 기능 및 사용법은 추후에 다른 카테고리에서 다룰 예정입니다.


이 포스트는 프론트엔드 개발자를 위한 자바스크립트(인사이트)에서 발췌한 내용이 포함되어 있습니다.
내용 전문이 아니기 때문에 자세하게 알고싶으신 분은 프론트엔드 개발자를 위한 자바스크립트(인사이트) 서적을 참고 하시길 바랍니다.