주기 함수와 grid step
이번에는 격자입니다. 무한 캔버스를 열었을 때 뒤에 은은하게 깔리는 그 grid 말입니다.
겉으로 보면 선을 잔뜩 그린 것 같지만, 원리는 간단합니다.
“일정 간격마다 같은 일이 반복된다.”
이게 주기 함수입니다. CSS에서는 repeating-linear-gradient로 만들 수 있고, 수학으로 보면 modulo 계산입니다. 이름만 들으면 좀 딱딱하지만, 사실 “몇 칸마다 한 번씩 줄 긋기”입니다.
grid는 반복 함수다
world 좌표에서 20px마다 세로선을 그리고 싶다고 해봅시다. 그러면 x 좌표가 20의 배수 근처일 때 선을 그리면 됩니다.
line when mod(worldX, step) ~= 0
mod는 나머지입니다. worldX를 step으로 나눴을 때 나머지가 0에 가까우면 grid line 위에 있는 셈입니다.
이 원리는 ruler tick에도 그대로 쓰입니다. 눈금도 결국 “일정 간격마다 표시되는 선과 숫자”입니다. grid와 ruler는 먼 친척이 아니라 거의 한집 식구입니다.
screen grid냐, world grid냐
무한 캔버스에서 중요한 질문이 하나 있습니다.
“이 격자는 화면에 붙어 있나요, 문서에 붙어 있나요?”
화면에 붙어 있으면 pan을 해도 grid가 움직이지 않습니다. 배경 무늬처럼 보이죠. 문서에 붙어 있으면 camera를 움직일 때 grid도 같이 밀려 보입니다. 디자인 에디터에서는 보통 world grid가 자연스럽습니다. 오브젝트가 world 좌표에 놓여 있으니 grid도 같은 세계에 있어야 합니다.
world 좌표를 screen 좌표로 바꾸는 기본식은 이렇습니다.
screenX = (worldX - cameraX) * zoom
visibleStart = floor(worldMin / step) * step
보이는 영역의 시작 world 좌표를 구하고, 그보다 작거나 같은 첫 tick부터 차례대로 그리면 됩니다.
CSS repeating-linear-gradient로 만들기
CSS에서는 반복 그라디언트로 grid를 만들 수 있습니다.
background-image:
repeating-linear-gradient(
to right,
transparent 0,
transparent 19px,
rgba(0, 0, 0, 0.12) 20px
);
이건 screen 기준 grid를 만들 때는 간단합니다. 하지만 world grid를 만들려면 background-position과 background-size를 camera, zoom에 맞춰 보정해야 합니다.
screenStep = worldStep * zoom
backgroundPosition = -camera * zoom
zoom이 커지면 grid 간격도 화면에서 넓어지고, zoom이 작아지면 촘촘해집니다. 너무 촘촘해지면 화면이 회색 먼지처럼 보이니, 일정 단계마다 world step을 바꿔줘야 합니다.
예를 들어 1, 2, 5, 10, 20, 50, 100... 같은 보기 좋은 간격을 고릅니다. ruler도 같은 정책을 공유하면 사용자가 보기에 안정적입니다.
데모에서 볼 것
데모에서는 pan과 zoom을 하면서 grid 선이 world 좌표에 고정되어 보이는지 확인합니다.
오늘의 핵심은 grid를 “선을 많이 그리는 기능”으로 보지 않는 것입니다. grid는 반복되는 좌표 함수입니다. 이 관점이 생기면 ruler, snap guide, background pattern까지 한 줄로 연결됩니다.