캔버스에서도 트랜스폼(transform)의 개념이 존재하는데 그럼 트랜스폼(transform)의 역할은 무엇인지 간략하게 알아보았다.
1. 복잡한 그래픽 변환
캔버스는 2D 그래픽을 다루는 HTML5의 요소로, 회전, 크기 조절, 이동, 기울이기 등 복잡한 그래픽 변환을 지원합니다. CSS만으로는 이런 복잡한 변환을 구현하기 어렵습니다. 특히 캔버스는 픽셀 단위의 정밀한 조작이 가능하여, 고급 그래픽 작업에 적합합니다.
2. 실시간 그래픽 처리
캔버스는 애니메이션과 같은 실시간 그래픽 처리에 강점이 있습니다. 트랜스폼을 사용하여 객체를 실시간으로 변형하고 업데이트할 수 있으며, 이는 게임 개발이나 데이터 시각화와 같은 분야에서 유용합니다.
3. 프레임워크 및 라이브러리 지원
캔버스는 많은 그래픽 프레임워크와 라이브러리에서 지원되며, 이들은 주로 트랜스폼을 사용하여 그래픽을 조작합니다. 예를 들어, Three.js나 Pixi.js 같은 라이브러리는 캔버스의 트랜스폼 기능을 활용하여 고성능 그래픽을 제공합니다.
4. CSS와의 차이점
CSS 트랜스폼은 주로 HTML 요소의 레이아웃과 스타일을 변형하는 데 사용됩니다. 반면, 캔버스 트랜스폼은 개별 픽셀 및 그래픽 요소를 변형하는 데 더 적합합니다. 캔버스는 그림을 그리는 맥락(context)을 조작하여 변형을 수행하므로, CSS로는 불가능한 정밀한 그래픽 작업을 수행할 수 있습니다.
5. 복합적인 트랜스폼 조합
캔버스에서는 여러 트랜스폼을 조합하여 사용하기가 용이합니다. 예를 들어, 회전 후 이동, 이동 후 스케일링 등의 복합적인 트랜스폼을 손쉽게 적용할 수 있습니다.
위의 이유들때문에 캔버스에서 트랜스폼(transform)을 사용한다고 한다.
트랜스폼(transform)을 실습하기 전에 save메서드와 restore메서드에 대해서 간략하게 알아보고 가자.
canvas의 모든 상태를 저장한다.
가장 최근에 저장된 canvas 상태를 복원한다.
restore은 여러 단계가 중첩이되는데, 이유는 canvas의 상태를 stack에 쌓이기 때문에 충첩이 가능하다.
간략하게 save와 restore를 사용하는 실습을 해보자.
const ctx = document.querySelector("canvas").getContext("2d");
// 검은색 배경
ctx.fillRect(0, 0, 150, 150);
// 검은색 배경 저장
ctx.save();
ctx.fillStyle = "#09F";
// 파란색 배경
ctx.fillRect(15, 15, 120, 120);
// 파란색 배경 저장
ctx.save();
ctx.fillStyle = "#FFF";
ctx.globalAlpha = 0.5;
// 하늘색 배경
ctx.fillRect(30, 30, 90, 90);
// 파란색 배경 restore
ctx.restore();
ctx.fillRect(45, 45, 60, 60);
// 검은색 배경 restore
ctx.restore();
ctx.fillRect(60, 60, 30, 30);
위의 코드를 실행하면 다음과 같은 결과를 얻을 수 있다. 코드에 주석을 해놓았으니 코드를 보면 이해하기 쉬울거다.
캔버스는 CSS처럼 고수준의 API가 아니기 때문에 캔버서는 무조건 (0, 0)의 좌표를 기준으로 그려진다.
때문에 내가 원하는 위치를 기준점으로 잡기위해서는 translate 함수를 사용해서 기준점을 이동한다.
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
let scaleValue = 1;
const draw = () => {
// 기준점을 350, 350 위치로 옮김
context.translate(350, 350);
// 그림
context.strokeRect(0, 0, 100, 100);
};
draw();
결과는 다음과 같다.
캔버스에서 스케일을 늘릴경우 css처럼 중앙을 기준으로 스케일이 늘어나지 않는다. 무조건 (0, 0)을 기준으로 스케일이 늘어난다. 이게 무슨 뜻인지는 아래를 그림을 보면 이해가 될것이다.
따라서 캔버스에서 CSS처럼 스케일을 중앙에서부터 늘려주는 작업을 하기 위해서는 내가 그리는 도형의 중앙을 계산해서 그려주어야한다.
가로 100 x 세로 100의 사각형을 그렸기 때문에 x,y 모두 -50씩 이동시켜서 그림을 그렸다.
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
let scaleValue = 1;
const draw = () => {
// 기준점을 350, 350 위치로 옮김
context.translate(350, 350);
// 그림
context.strokeRect(-50, -50, 100, 100);
};
draw();
원하는대로 스케일이 늘어나는것을 확인할 수 있다.
그려지는 것을 보면 스케일이 커지는데 기존에 캔버스에 그렸던 내용을 지우지 않아서 계속 겹쳐져서 그려지는것을 확인할 수 있는데 clearRect메서드를 이용해서 삭제를 해보자.
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
let scaleValue = 1;
const draw = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(350, 350);
context.scale(scaleValue, scaleValue);
scaleValue += 0.001;
context.strokeRect(-50, -50, 100, 100);
requestAnimationFrame(draw);
};
draw();
clearRect를 이용해서 삭제를 했으나 기준점이 (350, 350)이기 때문에 이상하게 지워지는 것을 확인할 수 있다. 우리가 그렸던 strokeRect는 (-50, -50) 위치로 이동했기 때문에 발생하는 문제이다.
이는 save와 restore을 사용해서 쉽게 처리가 가능하다.
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
let scaleValue = 1;
const draw = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
context.save();
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(350, 350);
context.scale(scaleValue, scaleValue);
scaleValue += 0.001;
context.strokeRect(-50, -50, 100, 100);
context.restore();
requestAnimationFrame(draw);
};
draw();
잘 지워지는지 확인해보자!
회전 효과를 한번 적용해보겠다.
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
let scaleValue = 1;
let rotationValue = 0;
const toRadian = (angle) => {
return (angle * Math.PI) / 180;
};
const draw = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
context.save();
// 기준점을 350, 350 위치로 옮김
context.translate(350, 350);
context.scale(scaleValue, scaleValue);
context.rotate(toRadian(rotationValue));
// 크기의 절반만큼 이동 후 그림
context.strokeRect(-50, -50, 100, 100);
// 왼쪽위에 작은 사각형의 위치를 초기화 해준다
// 변환을 초기화해준것임
context.setTransform(1, 0, 0, 1, 0, 0);
context.fillRect(10, 10, 30, 30);
context.restore();
scaleValue += 0.001;
rotationValue += 1;
requestAnimationFrame(draw);
};
draw();
잘 돌아가는지 확인해보자!
소스코드를 잘보면 이전 소스코드와는 다르게 setTransform메서드가 들어가있는데 이는 변환을 초기해주는것이다. 이게 무슨 말이냐면 setTransform을 주석처리하고 실행하면 다음처럼 실행이된다.
사실 setTransform을 사용해도 되고 restore이후에 fillRect를 그려도 동일한 효과를 나타낸다.
하지만 setTransform은 단위 행렬을 이용해서 변환을 시킬때 사용을 하기때문에 알아두면 좋다고한다.
이런 transform을 사용하면 다양한 효과를 줄 수 있는데 캔버스는 기본적으로 2d지만 3d처럼 보이게하는 방법도 있다고 한다.
다만 그러기 위해서는 계산식이 복잡하기 때문에 삼각함수, 행렬, 벡터 등을 기본적으로 학습이 되어있는게 좋다고 한다.
위의 실습에서 clearRect로 기존 그림들을 지우지 않으면 멋진 효과를 표현할 수 있다.
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
let scaleValue = 1;
let rotationValue = 0;
const toRadian = (angle) => {
return (angle * Math.PI) / 180;
};
const draw = () => {
// clearRect 주석처리
// context.clearRect(0, 0, canvas.width, canvas.height);
context.save();
// 기준점을 350, 350 위치로 옮김
context.translate(350, 350);
context.scale(scaleValue, scaleValue);
context.rotate(toRadian(rotationValue));
// 크기의 절반만큼 이동 후 그림
context.strokeRect(-50, -50, 100, 100);
// 왼쪽위에 작은 사각형의 위치를 초기화 해준다
// 변환을 초기화해준것임
context.setTransform(1, 0, 0, 1, 0, 0);
context.fillRect(10, 10, 30, 30);
context.restore();
scaleValue += 0.01;
rotationValue += 1;
requestAnimationFrame(draw);
};
draw();