Canvas API/Canvas tutorial/Compositing and clipping

김동현·2026년 3월 22일

안녕하세요! 캔버스 튜토리얼을 아주 훌륭하게 따라오고 계시네요. 이번 챕터는 캔버스의 고급 기술 중 하나인 '합성(Compositing)과 클리핑(Clipping)'입니다.

웹 프로필 사이트 같은 포트폴리오를 만드실 때, 사용자의 프로필 사진을 동그랗게 잘라내거나, 특정 영역만 하이라이트 해서 보여주고 싶을 때가 있죠? CSS로도 어느 정도 가능하지만, 캔버스 내에서 그래픽을 픽셀 단위로 직접 제어할 때는 오늘 배울 이 '클리핑'과 '합성' 기술이 필수적입니다. 자, 그럼 힘차게 시작해 볼까요!


합성(Compositing)과 클리핑(Clipping)

이전 (Previous) | 다음 (Next)

우리가 다루었던 이전 예제들에서는 항상 새로운 도형이 기존 도형 위에 덮어씌워지듯 그려졌습니다. 대부분의 상황에서는 이 방식만으로도 충분하지만, 여러 도형이 결합되는 순서나 방식을 제한하게 된다는 단점이 있습니다. 하지만 globalCompositeOperation 속성을 설정하면 이러한 기본 동작 방식을 마음대로 바꿀 수 있습니다. 게다가 clip 속성을 사용하면 도형에서 원하지 않는 부분은 싹둑 잘라내서 숨길 수도 있답니다.


이 문서에서는 (In this article)


globalCompositeOperation

우리는 단순히 기존에 그려진 도형 '뒤에' 새로운 도형을 그릴 수 있을 뿐만 아니라, 특정 영역을 마스킹(masking)해서 가리거나, 캔버스의 특정 섹션을 지워버리는 등(clearRect() 메서드처럼 사각형 모양으로만 지우는 것에 국한되지 않습니다) 훨씬 더 다양한 작업들을 수행할 수 있습니다.

globalCompositeOperation = type

새로운 도형을 그릴 때 적용할 합성(compositing) 작업의 종류를 설정합니다. 여기서 type은 사용할 수 있는 12가지 합성 작업 중 하나를 식별하는 문자열(string)입니다.

💡 강사의 실무 팁:
이 속성은 포토샵의 '블렌드 모드(Blend Mode)'나 '레이어 마스크'와 완벽하게 똑같은 역할을 합니다. 예를 들어 source-in을 사용하면 겹치는 부분만 남길 수 있고, destination-over를 쓰면 도장 찍듯 새로운 그림을 항상 배경 뒤로 보낼 수 있어요. 데이터를 시각화할 때 요소들이 예쁘게 겹치도록 만드는 핵심 키(Key)랍니다!


클리핑 경로 (Clipping paths)

클리핑 경로(clipping path)는 일반적인 캔버스의 도형을 그리는 것과 비슷하지만, 도형의 원하지 않는 부분을 숨겨주는 '마스크(mask)' 역할을 한다는 점이 다릅니다. 이 개념은 아래 이미지에 잘 시각화되어 있습니다. 빨간색 별 모양이 바로 우리의 클리핑 경로입니다. 이 별 모양 경로 바깥에 떨어지는 모든 것들은 캔버스에 그려지지 않습니다.

빨간색 윤곽선의 별이 그려진 캔버스. 별의 내부는 투명해서 별 안쪽의 그리드 사각형은 선명하게 보이지만, 별 바깥쪽에 있는 그리드 사각형들은 흐릿하게 처리되어 있습니다.

클리핑 경로를 위에서 살펴본 globalCompositeOperation 속성과 비교해 보자면, source-in이나 source-atop 합성 모드가 클리핑과 얼추 비슷한 효과를 낸다는 것을 알 수 있습니다. 하지만 이 둘 사이의 가장 중요한 차이점은, 클리핑 경로는 캔버스에 실제로 무언가를 그리지 않으며, 새로운 도형을 추가한다고 해서 클리핑 경로 자체가 영향을 받지도 않는다는 것입니다. 이런 특성 때문에 클리핑 경로는 제한된 영역 안에 여러 개의 도형을 그려 넣어야 할 때 아주 이상적입니다.

제가 앞선 도형 그리기(drawing shapes) 챕터에서는 경로를 다루는 메서드로 stroke()(선 그리기)와 fill()(색 채우기)만 언급했었지만, 사실 경로와 함께 사용할 수 있는 세 번째 메서드인 clip()이 숨어 있었습니다.

clip()

현재 그려지고 있는 경로를 '현재의 클리핑 경로'로 전환합니다.

경로를 닫고 그 경로를 따라 테두리를 그리거나 색을 채우는 대신에, clip()을 사용하면 해당 경로가 마스크로 변환됩니다. (주의: closePath()를 대체하는 것이 아닙니다. 경로를 닫은 후 stroke()fill() 대신 clip()을 호출하는 것입니다.)

기본적으로 <canvas> 요소는 캔버스 자체의 크기와 완벽하게 똑같은 크기의 기본 클리핑 경로를 가지고 있습니다. 다시 말해, 처음에는 아무런 클리핑(잘라내기) 효과도 일어나지 않는 상태라는 뜻이죠.

clip 예제

이 예제에서는 원형(circular) 클리핑 경로를 사용해서, 랜덤하게 그려지는 무수한 별들이 딱 특정 원형 영역 안에만 나타나도록 제한해 보겠습니다.

💡 강사의 핵심 팁:
이 기능은 포트폴리오 웹 프로필 사이트를 만들 때 정말 유용합니다! 정사각형 모양의 아바타나 프로필 사진을 캔버스에 렌더링해야 할 때, 둥근 원형 경로를 만들고 clip()을 호출한 뒤 이미지를 그리면, 아주 깔끔하고 예쁜 원형 프로필 이미지를 얻을 수 있습니다.

<canvas id="canvas" width="150" height="150"></canvas>
function draw() {
  const ctx = document.getElementById("canvas").getContext("2d");
  ctx.fillRect(0, 0, 150, 150); // 배경을 검은색으로 칠합니다.
  ctx.translate(75, 75); // 캔버스의 원점을 중앙으로 이동시킵니다.

  // 둥근 원형 클리핑 경로 생성하기
  ctx.beginPath();
  ctx.arc(0, 0, 60, 0, Math.PI * 2, true);
  ctx.clip(); // 짠! 이제부터 그려지는 모든 것은 이 원 안에서만 보입니다.

  // 배경 그리기 (그라디언트 적용)
  const linGrad = ctx.createLinearGradient(0, -75, 0, 75);
  linGrad.addColorStop(0, "#232256");
  linGrad.addColorStop(1, "#143778");

  ctx.fillStyle = linGrad;
  ctx.fillRect(-75, -75, 150, 150); // 사각형을 그리지만, 클리핑 때문에 원 모양으로만 보입니다!

  generateStars(ctx);
}

function generateStars(ctx) {
  for (let j = 1; j < 50; j++) {
    ctx.save();
    ctx.fillStyle = "white";
    ctx.translate(
      75 - Math.floor(Math.random() * 150),
      75 - Math.floor(Math.random() * 150),
    );
    drawStar(ctx, Math.floor(Math.random() * 4) + 2);
    ctx.restore();
  }
}

function drawStar(ctx, r) {
  ctx.save();
  ctx.beginPath();
  ctx.moveTo(r, 0);
  for (let i = 0; i < 9; i++) {
    ctx.rotate(Math.PI / 5);
    if (i % 2 === 0) {
      ctx.lineTo((r / 0.525731) * 0.200811, 0);
    } else {
      ctx.lineTo(r, 0);
    }
  }
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}

코드의 첫 몇 줄을 보면, 먼저 캔버스 크기만 한 검은색 사각형을 배경으로 깔고 원점을 캔버스 중앙으로 이동(translate)시킵니다. 그런 다음, arc 함수를 사용해 호를 그리고 clip()을 호출하여 원형 클리핑 경로를 만듭니다. 클리핑 경로는 캔버스의 save 상태에도 포함됩니다. 만약 클리핑을 취소하고 원래 캔버스 전체 영역을 다시 쓰고 싶다면, 클리핑 경로를 만들기 전에 save()로 상태를 저장해 두고 나중에 restore()로 불러오면 됩니다.

클리핑 경로가 생성된 이후에 그려지는 모든 것들은 오직 그 경로(마스크) 안쪽에서만 화면에 나타나게 됩니다. 다음에 그려지는 선형 그라디언트(linear gradient) 코드를 보면 이를 명확하게 알 수 있죠. 그라디언트를 칠한 후에는, 직접 만든 커스텀 함수인 drawStar()를 사용해서 크기와 위치가 무작위인 50개의 별을 그려줍니다. 여기서도 마찬가지로, 모든 별들은 우리가 방금 정의한 원형 클리핑 경로 안에서만 예쁘게 나타납니다.


반전 클리핑 경로 (Inverse clipping path)

캔버스 API에는 '반전 클리핑 마스크(마스크 한 부분만 구멍을 뚫어 투명하게 만드는 기능)' 같은 것은 존재하지 않습니다. 하지만, 캔버스 전체를 사각형으로 덮고 우리가 건너뛰고 싶은(구멍을 내고 싶은) 부분만 파낸 모양의 마스크를 정의하는 꼼수를 쓸 수는 있습니다.

구멍이 뚫린 도형 그리기(drawing a shape with a hole)에서 배웠던 것을 기억하시나요? 바깥쪽 도형을 그릴 때와는 반대 방향(opposite direction)으로 구멍(안쪽 도형)을 그려주면 됩니다. 아래 예제에서는 밤하늘에 별을 뚫어버리는(punch a hole) 트릭을 보여줍니다.

💡 강사의 실무 팁:
이 반전 클리핑 트릭은 꽤 멋진 기법입니다! 현재 공부 중이신 그래프 탐색(Graph Coloring) 알고리즘 시각화 도구를 만들 때, 전체 배경은 어둡게 덮어두고 현재 탐색 중인 특정 노드(Node) 영역만 동그랗게 구멍을 뚫어 밝게 하이라이트 하는 용도로 완벽하게 응용할 수 있습니다!

사각형 자체는 그리는 방향성이 없지만, 캔버스 내부적으로는 시계 방향으로 그린 것처럼 동작합니다. 기본적으로 arc 명령(호 그리기) 역시 시계 방향으로 진행되지만, 마지막 인자(counterclockwise)를 true로 설정해서 그 방향을 반대로 뒤집을 수 있습니다.

<canvas id="canvas" width="150" height="150"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  ctx.translate(75, 75);

  // 클리핑 경로 생성 (Inverse Clipping)
  ctx.beginPath();
  // 바깥쪽 사각형 (시계 방향으로 동작)
  ctx.rect(-75, -75, 150, 150); 
  // 안쪽 구멍 (반시계 방향으로 그려서 구멍을 냄!)
  ctx.arc(0, 0, 60, 0, Math.PI * 2, true); 
  ctx.clip(); // 구멍이 뚫린 사각형 마스크 완성

  // 배경 그리기
  const linGrad = ctx.createLinearGradient(0, -75, 0, 75);
  linGrad.addColorStop(0, "#232256");
  linGrad.addColorStop(1, "#143778");

  ctx.fillStyle = linGrad;
  // 사각형을 칠하지만 가운데 동그란 구멍 부분은 칠해지지 않고 투명하게 남습니다.
  ctx.fillRect(-75, -75, 150, 150); 

  generateStars(ctx); // 아까 썼던 별 그리기 함수
}

// generateStars와 drawStar 함수 코드는 위와 동일하므로 생략합니다.
function generateStars(ctx) { ... }
function drawStar(ctx, r) { ... }

draw();

위 코드를 실행해보면, 캔버스의 모서리 부분(사각형 영역)에는 우주 배경과 별들이 채워져 있고, 정작 중앙의 커다란 원 부분은 뻥 뚫려서 하얗게(투명하게) 남는 것을 확인할 수 있습니다.

이전 (Previous) | 다음 (Next)


MDN 개선에 참여하기 (Help improve MDN)

이 페이지가 도움이 되었나요? (Was this page helpful to you?)
[ 예 (Yes) ][ 아니오 (No) ]

링크


어떠셨나요? 생각보다 코드가 복잡해 보일 수 있지만, clip() 함수 하나로 그래픽의 표현 범위가 무궁무진하게 넓어지는 것을 확인하셨을 겁니다.

이전에 배웠던 변환(Transform)과 상태 저장(save/restore), 그리고 오늘 배운 클리핑까지 조합하면 캔버스 위에서 못 그릴 UI가 없습니다! 이해가 안 되는 예제 코드나 실무 응용법에 대해 추가로 궁금한 점이 있다면 언제든 물어보세요!

profile
프론트에_가까운_풀스택_개발자

0개의 댓글