사전 캔버스
용어함정

캔버스

gabury1고친 사람 github-actions[bot]

캔버스는 그림을 칠할 바탕을 내어 줍니다. 웹에서는 이 이름이 서로 다른 두 물건을 부릅니다. 하나는 브라우저가 문서 전체를 칠하는 끝없는 평면입니다. 다른 하나는 스크립트가 픽셀을 한 점씩 직접 칠하는 그림판 요소입니다.

쉽고 빠른 이해

무슨 일을 하는 물건인가 — 그림이 올라갈 바탕입니다. 웹 페이지의 글자와 상자가 칠해지는 평면도 캔버스입니다. 차트나 게임을 그리는 그림판 태그도 캔버스입니다. 이름만 같고 쓰임은 다릅니다.

왜 이렇게 하나 — 브라우저는 창 크기와 상관없이 문서를 칠할 바탕이 필요합니다. 창은 그 바탕의 일부만 비춥니다. 그림판 태그는 점이 수천 개인 그림을 태그 수천 개 없이 그리려고 둡니다.

어떻게 도나

  1. 브라우저가 문서의 상자들을 평면 위에 칠합니다. 창은 그중 일부를 보여 줍니다
  2. 그림판 태그는 스크립트가 붓 역할을 하는 객체를 꺼내 픽셀을 칠합니다
  3. 그림판에 다른 사이트의 그림을 그리면 그 뒤로는 픽셀을 읽을 수 없게 막힙니다

대가 — 그림판은 칠한 뒤 픽셀만 기억합니다. 도형을 옮기려면 지우고 다시 그려야 합니다. 두 뜻을 섞어 부르면 대화가 엇갈립니다.

상세

이 절은 「캔버스」가 가리키는 두 물건, 문서를 칠하는 평면과 스크립트로 칠하는 그림판 요소를 차례로 봅니다.

캔버스가 가리키는 물건들

맥락마다 캔버스가 담는 것이 다릅니다. 공통점은 하나입니다. 무언가가 칠해지는 바탕이라는 점입니다.

맥락 캔버스가 가리키는 것 예
문서 렌더링 브라우저가 문서를 칠하는 끝없는 평면 스크롤해서 보는 웹 페이지 전체
그림판 요소 스크립트가 픽셀을 칠하는 사각형 그림판 차트 · 게임 화면 · 사진 편집기
디자인 도구 도형과 화면 시안을 올려놓는 작업판 화면 설계 도구의 넓은 작업 영역

백엔드 개발자가 문서에서 만나는 것은 앞의 둘입니다.

문서를 칠하는 평면

브라우저는 문서를 받으면 글자와 상자를 어디에 어떤 모양으로 놓을지 정합니다. 그 규칙을 적는 언어가 CSS(Cascading Style Sheets, 캐스케이딩 스타일 시트)입니다. CSS 를 따라 계산한 상자들이 최종적으로 칠해지는 평면이 캔버스입니다.

전시장 벽을 떠올리면 됩니다. 벽은 끝없이 길게 이어져 있습니다. 관람객은 창문 하나로 그 벽의 한 부분만 봅니다. 창문 앞에서 옆으로 걸어가면 벽의 다른 부분이 보입니다.

캔버스는 가로로도 세로로도 끝이 없는 평면입니다. 문서가 칠해지는 곳은 그중 유한한 영역입니다. 화면에 보일지 종이에 찍힐지에 따라 브라우저가 그 영역의 크기를 정합니다.

평면을 끝없이 두는 까닭은 문서가 창보다 길 수 있어서입니다. 칠할 곳이 창 크기에 묶이면 창 밖으로 넘친 부분은 칠할 곳이 없습니다. 그래서 칠하는 평면과 보는 창을 따로 둡니다.

사용자가 캔버스를 들여다보는 창이 뷰포트입니다. 브라우저 창에서 문서가 보이는 사각형이 곧 뷰포트입니다. 문서가 뷰포트보다 크면 브라우저는 스크롤 막대를 붙입니다. 스크롤은 뷰포트가 캔버스의 어느 부분을 비출지 옮기는 일입니다.

flowchart TD
    subgraph CV["캔버스 · 끝없는 평면"]
        subgraph DOC["문서가 칠해진 영역"]
            VP["뷰포트가 비추는 부분"]
        end
    end
    VP --> W["브라우저 창에 보이는 화면"]

그림은 세 겹의 포함 관계를 보입니다. 캔버스 안에 문서가 칠해진 영역이 있습니다. 그 안의 한 조각만 뷰포트를 거쳐 화면에 나옵니다.

평면에 무엇을 어디에 둘지 먼저 계산하는 단계가 레이아웃입니다. 레이아웃이 상자(박스)의 크기와 위치를 정하면, 그 상자들이 캔버스 위에 칠해집니다.

캔버스 배경은 루트 요소가 정한다

루트 요소는 문서의 맨 바깥 요소입니다. HTML(HyperText Markup Language, 하이퍼텍스트 마크업 언어) 문서에서는 <html> 요소가 루트 요소입니다.

루트 요소에 준 배경은 루트 요소의 상자에만 칠해지지 않습니다. 캔버스 전체로 옮겨 가 평면을 다 덮습니다. 루트 요소가 짧아도 창 아래 끝까지 같은 색이 칠해집니다.

아래 규칙은 <html> 의 높이를 200픽셀로 묶고 배경을 남색으로 줍니다.

CSS
html {
  height: 200px;
  background: navy; /* 창 전체가 남색 */
}

<html> 상자는 200픽셀에서 끝납니다. 창이 그보다 길어도 남은 아래쪽까지 남색입니다. 배경이 루트 요소의 상자가 아니라 캔버스에 칠해졌기 때문입니다.

HTML 문서에는 한 단계가 더 있습니다. <html> 에 배경이 없으면 브라우저는 <body> 의 배경을 가져다 캔버스에 칠합니다. 흔히 body 에 배경색을 주는데도 창 전체가 칠해지는 까닭입니다.

스크립트로 칠하는 canvas 요소

HTML 에는 이름이 같은 <canvas> 태그가 있습니다. 이 태그는 페이지 안에 픽셀로 된 사각형 그림판을 하나 만듭니다. 이 그림판이 canvas 요소입니다.

그림판은 가로세로로 칸이 나뉜 픽셀 격자입니다. 이런 격자 그림이 비트맵입니다. width 와 height 속성이 격자의 가로 칸 수와 세로 칸 수를 정합니다. 둘을 안 주면 가로 300, 세로 150 칸이 됩니다.

태그만 놓으면 빈 판입니다. 그리려면 그림판에서 렌더링 컨텍스트를 꺼내야 합니다. 렌더링 컨텍스트는 그리기 함수와 지금 쓰는 색 같은 설정을 한데 담은 객체입니다. 붓과 물감을 한 벌로 쥐는 셈입니다.

getContext("2d") 로 꺼내면 평면 도형을 그리는 2D(2차원) 컨텍스트가 나옵니다. 사각형 칠하기, 선 긋기, 픽셀 읽기 같은 함수가 이 컨텍스트에 달려 있습니다. Canvas API(Application Programming Interface, 응용 프로그램 인터페이스)는 이 2D 컨텍스트의 함수 묶음을 가리킵니다.

getContext("webgl") 로 꺼내면 WebGL 컨텍스트가 나옵니다. WebGL 은 GPU(Graphics Processing Unit, 그래픽 처리 장치)로 그리는 API 입니다. Canvas API 와는 따로 부르는 이름입니다. 브라우저가 모르는 이름을 대면 null 이 돌아옵니다.

아래 코드는 빨간 사각형을 칠하고 그 안의 픽셀 하나를 읽어 봅니다.

JavaScript
const c = document.querySelector("canvas");
const ctx = c.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 50, 30);
const px = ctx.getImageData(10, 10, 1, 1);
px.data; // [255, 0, 0, 255]

fillRect 는 왼쪽 위 모서리에서 가로 50, 세로 30 픽셀을 칠합니다. getImageData 는 (10, 10) 에 있는 픽셀 하나를 꺼냅니다. 돌아온 네 값은 빨강·초록·파랑의 세기와 불투명도입니다. 빨강만 가득하고 완전히 불투명하다는 뜻입니다.

그린 뒤에는 픽셀만 남는다

canvas 요소는 칠한 결과만 기억합니다. 「여기 사각형이 있다」는 정보는 칠하는 순간 사라집니다. 픽셀 색만 남습니다. 이렇게 그리는 방식을 즉시 모드라고 합니다.

그래서 사각형을 옆으로 옮기려면 판을 지우고 새 위치에 다시 그려야 합니다. 움직이는 그림은 한 장면마다 이 일을 되풀이합니다.

반대편에 SVG(Scalable Vector Graphics, 확장 가능한 벡터 그래픽)가 있습니다. SVG 는 도형 하나하나가 페이지 안의 요소로 남습니다. 브라우저는 그 요소들을 DOM(Document Object Model, 문서 객체 모델)이라는 트리로 들고 있습니다. 도형의 속성만 바꾸면 브라우저가 알아서 다시 그립니다.

canvas 요소 SVG
기억하는 것 픽셀 색 도형 하나하나
도형을 옮기려면 지우고 다시 그린다 요소의 속성을 바꾼다
도형이 수만 개면 판의 픽셀 수는 그대로다 요소 수만큼 메모리와 다시 그리는 일이 는다
도형 클릭 좌표로 어느 도형인지 직접 계산한다 요소마다 클릭을 받는다

점이 수만 개인 차트나 게임 화면에 canvas 요소를 쓰는 까닭이 셋째 줄입니다. 넷째 줄은 그 대가입니다. 그림 속 글자도 픽셀일 뿐이라 선택하거나 복사할 수 없습니다.

그림판의 칸 수와 화면에 보이는 크기는 따로 정해집니다. CSS 로 그림판을 칸 수보다 크게 늘리면 픽셀이 함께 늘어나 그림이 흐려집니다. 선명하게 보이려면 보이는 크기만큼 칸 수도 늘려야 합니다.

다른 출처 그림이 canvas 요소를 오염시킨다

이 절은 다른 사이트의 그림을 canvas 요소에 그렸을 때 픽셀 읽기가 막히는 일을 봅니다. 먼저 「다른 사이트」를 가르는 단위부터 세웁니다.

출처는 주소 앞머리의 세 부분을 묶은 단위입니다. 스킴과 호스트와 포트입니다. 스킴은 https 처럼 주소 맨 앞에 오는 부분입니다. 셋 중 하나라도 다르면 다른 출처입니다. https://a.example 과 https://b.example 은 호스트가 달라 다른 출처입니다.

웹에는 동일 출처 정책이 있습니다. 한 출처에서 온 스크립트가 다른 출처의 데이터를 읽지 못하게 막는 규칙입니다. 한 브라우저에 열린 여러 사이트가 서로의 데이터를 엿보지 못하게 하려고 둡니다.

페이지는 다른 출처의 그림을 <img> 로 보여 줄 수 있습니다. 보여 주기만 하고 픽셀을 읽지는 못합니다. 그런데 그 그림을 canvas 요소에 그린 뒤 getImageData 로 읽으면 이 제한을 비켜 가게 됩니다.

그래서 브라우저는 다른 출처의 그림이 그려진 그림판에 표시를 남깁니다. 이 상태가 오염된 캔버스입니다. 이 이름의 캔버스는 canvas 요소 쪽입니다. 오염된 그림판에서 픽셀을 꺼내는 함수는 SecurityError 를 던집니다. 픽셀을 읽는 getImageData, 그림을 문자열로 바꾸는 toDataURL, 파일 덩어리로 바꾸는 toBlob 이 모두 막힙니다.

막는 이유는 브라우저가 사용자의 권한을 쥐고 있기 때문입니다. 브라우저는 다른 사이트에 요청할 때 사용자의 로그인 쿠키를 함께 보냅니다. 막지 않으면 악성 페이지가 사용자에게만 보이는 다른 사이트의 사진을 그림판에 그려 픽셀을 빼 갈 수 있습니다.

아래 코드는 a.example 페이지에서 b.example 의 그림을 그리고 꺼내 봅니다.

JavaScript
const img = new Image();
img.src = "https://b.example/me.png";
img.onload = () => {
  ctx.drawImage(img, 0, 0);
  c.toDataURL(); // SecurityError
};

그림은 잘 그려집니다. 그림을 문자열로 꺼내려는 마지막 줄에서 막힙니다.

이 막힘을 푸는 방법이 CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)입니다. 그림을 내주는 서버가 「이 출처라면 읽어도 된다」는 응답 헤더를 붙입니다. 그 헤더가 Access-Control-Allow-Origin 입니다.

페이지 쪽도 할 일이 있습니다. 그림을 부르기 전에 img.crossOrigin = "anonymous" 를 걸어 둡니다. 그래야 브라우저가 그 그림을 CORS 요청으로 보내고 서버의 허락을 확인합니다. "anonymous" 는 쿠키 같은 사용자 정보를 싣지 않고 요청한다는 뜻입니다.

flowchart TD
    A["canvas 요소에 그림을 그린다"] --> B{"다른 출처의 그림인가"}
    B -->|아니다| C["깨끗한 그림판 · 픽셀을 읽을 수 있다"]
    B -->|그렇다| D{"페이지가 crossOrigin 을 걸고 서버가 허락했나"}
    D -->|둘 다 됐다| C
    D -->|하나라도 빠졌다| E["오염된 캔버스 · 픽셀 읽기가 막힌다"]

갈림은 두 번입니다. 같은 출처의 그림이면 바로 깨끗합니다. 다른 출처라도 페이지가 crossOrigin 을 걸고 서버가 허락했으면 깨끗합니다. 어느 쪽에도 들지 않을 때만 오염됩니다.

백엔드 쪽에서 이 흐름을 만나는 때는 이미지 서버를 맡을 때입니다. 프론트엔드가 그림판에 올린 그림을 편집해 다시 올리는 기능이 있다면, 이미지 응답에 CORS 헤더가 붙어 있어야 합니다. 헤더가 없으면 저장 버튼을 누르는 순간 SecurityError 가 납니다.

WebGL 은 더 엄격합니다. 허락받지 않은 다른 출처 그림은 GPU 로 올리는 단계에서부터 거절됩니다.

어느 뜻인지 가르는 단서

대화나 문서에서 「캔버스」가 나오면 함께 붙은 낱말을 봅니다. 대개 그것만으로 어느 뜻인지 갈립니다.

함께 나오는 말 뜻
뷰포트 · 스크롤 · 루트 요소 · 배경 · 박스 문서를 칠하는 평면
<canvas> · getContext · 2D · WebGL · 픽셀 · 오염 canvas 요소
시안 · 작업판 · 확대 축소 · 도형 배치 디자인 도구의 작업판

두 뜻은 이름만 같은 것이 아니라 겹쳐 놓일 수 있습니다. <canvas> 태그도 페이지 안의 상자 하나입니다. canvas 요소는 레이아웃을 거쳐 문서의 캔버스 평면 위에 놓입니다. 평면 위에 그림판 하나가 걸려 있는 모습입니다.

관련 항목

캔버스 평면에 문서를 칠하는 렌더링 단계

렌더링 · 레이아웃 · 박스 트리 · 원본 트리 · 페인트 · 합성 · CSS

캔버스 평면을 비추고 둘러싼 보기 영역

뷰포트 · 스크롤 · 루트 요소 · 초기 포함 블록 · 사용자 에이전트 · HTML

canvas 요소에 그리는 API

canvas 요소 · Canvas API · 렌더링 컨텍스트 · WebGL · WebGPU · OffscreenCanvas · ImageData · GPU

canvas 요소와 겨루는 그리기 방식

SVG · DOM · 즉시 모드 · 유지 모드 · 비트맵 · 벡터 그래픽스

캔버스 픽셀 읽기를 막는 보안 규칙

동일 출처 정책 · 출처 · CORS · 오염된 캔버스 · SecurityError · crossOrigin 속성

다른 이름: canvas