[HTML] Canvas를 이용해서 가장 간단한 도형부터 그려보자

·2025년 3월 28일

Study Note ✍🏻

목록 보기
54/63

Canvas

HTML5 Canvas는 웹 페이지에서 그래픽을 그리기 위한 HTML 요소다. JavaScript를 사용하여 동적으로 그래픽을 생성하고 조작할 수 있는 강력한 도구로, 다양한 웹 기반 그래픽 애플리케이션을 만들 수 있도록 지원한다. 2D 그래픽, 애니메이션, 인터렉션, 차트 및 그래프, 게임 개발, 이미지 처리, 그래픽 편집기, 그림판 등 다양한 활용 범위가 있다.

<canvas id="tutorial" width="150" height="150"></canvas>

id: JavaScript에서 <canvas> 요소를 참조할 때 사용
width, height: 캔버스의 크기 지정 (기본값: 300x150) width와 height는 CSS가 아니라 속성(attribute)으로 지정해야 한다. CSS로 크기를 조정하면 그림이 왜곡될 수 있다.

<canvas> 자체는 그림을 그리는 기능이 없고 JavaScript를 사용해 그래픽을 그려야 한다. <canvas> 요소는 getContext() 메서드를 이용해서, 랜더링 컨텍스트와 그리기 함수들을 사용할 수 있다. 2D 그래픽의 경우, CanvasRenderingContext2D 을 얻기위해 "2d"로 지정한다.

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d"); // 2D 컨텍스트 가져오기

<canvas> 요소는 인터넷 익스플로러 9 이하의 버전이나 텍스트기반 브라우저 등과 같은, 캔버스를 지원하지 않는 오래된 브라우저들을 위한 대체 컨텐츠를 정의하기 쉽다. <canvas>를 지원하는 브라우저는 컨테이너 내부의 내용을 무시하고 캔버스를 정상적으로 렌더링한다.

<canvas id="stockGraph" width="150" height="150">
  current stock price: $3.15 +0.15
</canvas>

<canvas id="clock" width="150" height="150">
  <img src="images/clock.png" width="150" height="150" alt="" />
</canvas>

대체 컨텐츠가 제공되는 방식때문에, <img> 요소와 달리, <canvas> 요소는 닫는 태그(</canvas>)가 필요하다.

대체 콘텐츠는 <canvas>를 지원하지 않는 브라우저에 표시된다. 스크립트 역시 간단하게 getContext() 메소드의 존재 여부를 테스트함으로써 프로그래밍 방식으로 지원하는지를 확인할 수 있다.

var canvas = document.getElementById("tutorial");

if (canvas.getContext) {
  var ctx = canvas.getContext("2d");
  // drawing code here
} else {
  // canvas-unsupported code here
}

사각형 그리기

ctx를 생성했다면, fillRect(x, y, width, height) 함수를 사용해 캔버스 위에 사각형을 그려볼 수 있다. 이는 x, y 좌표 위치에 가로, 세로 Npx의 사각형을 채우라는 의미이다.

ctx.fillStyle = "blue";    // 채우기 색상 설정
ctx.fillRect(50, 50, 150, 100);  // (x, y, width, height)

선 그리기를 할 땐, 다음과 같이 그릴 수 있다.

ctx.beginPath();
ctx.moveTo(50, 50);  // 시작 좌표
ctx.lineTo(200, 200);  // 끝 좌표
ctx.strokeStyle = "red";  // 선 색상
ctx.lineWidth = 5;  // 선 두께
ctx.stroke();  // 선 그리기

beginPath(): 새로운 경로 생성
moveTo(x, y): 시작 위치 지정
lineTo(x, y): 선 끝 위치 지정
strokeStyle: 선 색상 설정
lineWidth: 선 두께 설정
stroke(): 선을 그려 화면에 표시

beginPath()는 새로운 그림을 그릴 때 기존의 경로(path)를 초기화하는 역할을 한다. <canvas>는 상태를 계속 유지하기 때문에, 새로운 선이나 도형을 그릴 때 beginPath()를 호출하지 않으면 기존의 경로와 연결될 수 있다.

원 그리기

원을 그릴 땐, arc(x, y, radius, startAngle, endAngle, counterclockwise)를 이용한다. 이는 x, y 좌표 위치에 반지름이 Rpx인 원을 startAngle부터 endAngle만큼 그리라는 의미이다. canvas에서 각도를 나타낼 때는 일반적으로 사용하는 degree 대신 radian을 사용한다. 만약 degrees를 canvas에서 사용하고자 한다면, 각도를 라디안으로 변환하는 수식을 사용하여 처리할 수 있다. (Math.PI / 180) * deg

ctx.beginPath();
ctx.arc(150, 150, 50, 0, Math.PI * 2); // (x, y, 반지름, 시작각, 끝각)
ctx.fillStyle = "green";
ctx.fill();  // 원 내부 색상 채우기
ctx.stroke();  // 테두리 그리기

글자와 이미지 출력

그 외 글자와 이미지를 출력할 경우에는 다음과 같이 그릴 수 있다.

ctx.font = "30px Arial";
ctx.fillStyle = "black";
ctx.fillText("Hello Canvas!", 50, 200);
const img = new Image();
img.src = "image.png"; // 이미지 경로
img.onload = function () {
  ctx.drawImage(img, 50, 50, 100, 100); // (x, y, width, height)
};

이미지를 그릴 땐 img.onload를 함께 써야 한다. 그 이유는 이미지가 아직 메모리에 없을 수 있기 때문이다. img.src = "image.png"를 실행하면 비동기적으로 이미지가 로드되어 바로 다음 줄(ctx.drawImage)을 실행할 수 있다. 이 시점에서 이미지가 아직 로드되지 않았다면 drawImage가 실패한다.
아래와 같이 Promise를 활용해 구현할 수도 있다.

function loadImage(src) {
  return new Promise((resolve) => {
    const img = new Image();
    img.src = src;
    img.onload = () => resolve(img);
  });
}

loadImage("image.png").then((img) => {
  ctx.drawImage(img, 50, 50, 100, 100);
});

애니메이션 구현

canvas에 그려진 그래픽이 애니메이션되는 것처럼 보이는 원리는 매 프레임마다 기존의 장면을 지우고 새로운 장면을 빠르게 업데이트하여 연속으로 재생하면서 움직이는 것처럼 보이게 하는 것이다. requestAnimationFrame은 웹 애니메이션 및 그래픽 처리에 사용되는 웹 브라우저 함수다. 이 함수를 사용하면 브라우저가 리페인트하기 전에 콜백 함수를 호출하여 애니메이션 및 그래픽 작업을 부드럽게 처리할 수 있다. 이 함수는 1초에 현재 모니터의 주사율 만큼 동작을 반복한다. 때문에 x를 1px씩 이동시키는 애니메이션을 작성했다면, 주사율에 따라 어떤 사용자는 1초에 144px을 이동하게 되고 다른 사용자는 60px을 이동하는 것을 관찰하게 된다. 이렇게 사용자의 환경마다 다른 애니메이션이 제공되어 버린다. 이를 해결하려면 각 프레임 간의 시간 차이(deltaTime)를 이용해서 속도를 일정하게 유지해야 한다.

let deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;

currentTime은 requestAnimationFrame(animate)의 콜백 함수에서 전달하는 현재 시간(밀리초 단위, ms) 이고 lastTime은 이전 프레임에서 저장한 시간이다. 이 둘을 통해 현재 프레임과 이전 프레임 사이에 걸린 시간(밀리초 단위) 을 구할 수 있게 된다. ms를 쉽게 ㅅ계산하기 위해 1000으로 나눠 초(second) 단위로 변환해준다.

중력과 가속도

이제 애니메이션에 현실성을 주기 위해 중력과 가속도를 고려해 애니메이션을 구현할 수 있다. 중력은 물체가 아래로 끌려가는 힘으로 보통 2D 물리 시뮬레이션에서 y축 방향으로 작용하는 일정한 값으로 설정된다. velocityY+=gravity 즉, 중력 값을 속도(velocity)에 계속 더해주면 시간이 지날수록 속도가 증가하면서 물체가 더 빠르게 떨어지게 된다.
위에서 언급했듯이 일반적으로 애니메이션 프레임 속도는 requestAnimationFrame을 사용하기 때문에, 각 프레임이 실행되는 시간 간격이 달라질 수 있다. 이를 해결하기 위해 deltaTime을 활용해준다.

velocityY+=gravity×deltaTime

    // 중력 적용 (자유낙하 운동)
    ball.velocityY += ball.gravity * deltaSeconds;
    ball.y += ball.velocityY * deltaSeconds;
    
    // 바닥 충돌 체크
    if (ball.y + ball.radius > canvas.height) {
        ball.y = canvas.height - ball.radius;  // 바닥에 닿으면 위치 보정
        ball.velocityY *= -ball.bounce;        // 속도를 반전하여 튕기기
    }

중력을 통해 y값을 적용해주고, 바닥에 충돌했을 때 ball.y = canvas.height - ball.radius을 이용해 공이 딱 바닥에 붙어있어 보이도록 보정을 해준다. (뚫고 나가지 않게 하기 위함) bounce탄성 계수로, 튕길 때 속도가 얼마나 줄어드는지 조절하는 속성이다. ball.velocityY *= -ball.bounce를 보면 음수를 곱해준다. 즉, 방향을 반전시키는 것이다. 그렇게해서 올라간 공이 또 다시 중력 계수에 의해서 점차적으로 아래로 내려오게 된다.

profile
Frontend🍓

0개의 댓글