Chapter 04

레이아웃 그리드

책의 그리드는 종이 크기를 알고 짠다. A5라면 148 × 210 mm, 그 안에 여백과 판면과 단을 나누면 끝이다. 웹의 그리드는 종이 크기를 모른 채 짜야 한다. 같은 페이지가 폭 360의 휴대폰에서도, 1,440의 모니터에서도 열린다. 그래서 웹 그리드는 숫자 몇 개가 아니라 규칙이다. 단은 몇 개인가, 단 사이는 얼마인가, 화면 가장자리는 얼마나 비우는가, 화면이 아무리 넓어져도 어디서 멈추는가. 이 장은 그 규칙을 하나씩 세우고, 화면 폭을 끌어 가며 규칙이 버티는지 시험한다. 그리고 규칙을 일부러 깨는 한 가지 요소를 고른다.

단, 거터, 바깥 여백

TypeBook 9장에서 지면을 여백·판면·단·거터·모듈로 미리 나눈 보이지 않는 격자를 세웠다. 웹 화면의 그리드도 같은 생각에서 나왔다. 화면을 세로로 긴 몇 개의 칸으로 나누고, 모든 요소의 왼쪽 끝과 오른쪽 끝을 그 칸의 경계에 맞춘다. 이렇게 세로 칸으로 나눈 그리드를 열 그리드(Column grid)라고 부른다. 세로 칸 하나가 단(Column)이고, 단과 단 사이의 빈 띠가 거터(Gutter), 그리드 전체와 화면 가장자리 사이의 빈 자리가 바깥 여백(Margin)이다. 3장의 CSS margin과 이름이 같지만, 여기서는 그리드의 양옆 빈 자리를 가리킨다. 실제 코드에서는 페이지 바탕 상자의 padding으로 만드는 경우가 많다.

3단 카드 바깥 여백 단 거터 콘텐츠 폭(최대 폭까지)
그림 1. 열 그리드의 해부. 브라우저 창 안에서 양옆 바깥 여백을 빼면 콘텐츠 폭이 남고, 그것을 단과 거터로 나눈다. 단 열두 개 사이에는 거터가 열한 개다. 카드는 단 몇 개와 그 사이 거터를 함께 차지한다(그림의 카드는 3단). 화면이 아주 넓을 때 콘텐츠 폭이 끝없이 늘지 않도록 최대 폭을 둔다.

종이 그리드와 가장 크게 다른 것은 마지막 줄의 "최대 폭"이다. 종이는 커지지 않지만 화면은 커진다. 1,920 픽셀 모니터에서 본문 글줄이 화면 끝에서 끝까지 이어지면 TypeBook 6장에서 본 대로 한 줄이 너무 길어 읽기 어렵다. 그래서 웹 그리드는 대개 콘텐츠 폭에 상한을 두고, 그보다 넓은 화면에서는 양옆 바깥 여백이 늘어나도록 한다. 반대로 좁은 화면에서는 바깥 여백이 최솟값까지 줄어들고, 단이 좁아진다. 화면 폭이 바뀔 때 무엇이 늘고 무엇이 고정되는지, 이것이 웹 그리드를 정의하는 질문이다.

12단의 산술

그리드를 숫자로 정하면 단 폭은 계산으로 나온다. 화면 폭에서 양옆 바깥 여백을 빼고, 거터를 모두 빼고, 남은 것을 단 수로 나눈다.

$$c = \frac{V - 2M - (n-1)\,G}{n}, \qquad S_k = k\,c + (k-1)\,G$$
\(V\): 화면(뷰포트) 폭, \(M\): 한쪽 바깥 여백, \(G\): 거터, \(n\): 단 수, \(c\): 단 폭, \(S_k\): 단 \(k\)개에 걸친 요소의 폭(사이 거터 \(k-1\)개 포함). 예: \(V = 1280,\ M = 40,\ G = 24,\ n = 12\)이면 \(c = (1280 - 80 - 264)/12 = 78\), 3단에 걸친 카드는 \(3 \times 78 + 2 \times 24 = 282\).

아래 시뮬레이터에서 가상 브라우저 창의 오른쪽 가장자리 손잡이를 끌어 화면 폭을 바꾸고, 슬라이더로 단 수, 거터, 바깥 여백을 바꾼다. 분홍 칸이 단이다. 아래 줄의 카드는 각각 3단에 걸쳐 있다.

SIMULATOR

화면 폭을 끌어 보는 열 그리드

창의 오른쪽 가장자리를 끈다
화면 폭 V—
단 폭 c—
3단 · 4단 · 6단에 걸친 폭—
판정—
해볼 것: ① 처음 상태(V 1280, 12단, 거터 24, 바깥 여백 40)에서 단 폭이 정확히 78이 되는 것을 본다. ② 창을 왼쪽으로 끌어 360까지 줄인다. 단 폭이 1.3으로 쪼그라들어 거터(24)보다 훨씬 좁아진다. 바깥 여백을 16으로 줄여도 5 남짓이다. 이런 그리드에 3단 카드를 놓으면 글 한 줄도 들어가지 않는다. 좁은 화면에 넓은 화면의 12단을 그대로 쓸 수 없다는 뜻이다. ③ 창을 1,600까지 늘린다. 단도 카드도 끝없이 넓어진다. 최대 폭이 없는 그리드다. ④ 단 수를 5나 7로 바꾼다. 3단 카드가 몇 장 들어가는지, 남는 단이 생기는지 본다. (화면 폭은 이 그림 속 가상 창의 폭이다. 단 폭은 소수점까지 계산하고, 브라우저도 소수 픽셀 폭을 그대로 다룬다.)

왜 하필 12단이 흔할까. 12는 1, 2, 3, 4, 6, 12로 나누어떨어진다. 한 줄에 카드 2장(6단씩), 3장(4단씩), 4장(3단씩), 6장(2단씩)을 남는 단 없이 놓을 수 있고, 8단 본문과 4단 곁글처럼 2:1로 나눌 수도, 9단과 3단처럼 3:1로 나눌 수도 있다. 10단은 3으로 나누어지지 않고, 16단은 3과 6으로 나누어지지 않는다. 12는 이 정도 크기의 수 중에서 약수가 가장 많은 축에 든다. 단 수가 많다고 좋은 것도 아니다. 단이 많으면 선택지가 늘지만 실제로 쓰는 조합은 몇 개뿐이고, 단 폭이 거터보다 좁아지면 그리드는 쓸모를 잃는다.

단 수남는 단 없이 나눌 수 있는 한 줄의 카드 수흔한 쓰임
41, 2, 4휴대폰 화면(단이 넓고 거터가 좁다)
61, 2, 3, 6중간 폭 화면, 간단한 페이지
81, 2, 4, 8태블릿 폭에서 쓰는 체계가 있다
121, 2, 3, 4, 6, 12넓은 화면 웹의 사실상 관례
161, 2, 4, 8, 16정보가 아주 많은 관리 화면
960 픽셀의 시대 2000년대 후반 많은 웹사이트는 폭 960 픽셀의 고정된 판 위에 만들어졌다. 당시 흔하던 1,024 픽셀 모니터에서 스크롤 막대와 창 테두리를 빼고도 들어가는 폭이었고, 960은 2, 3, 4, 5, 6, 8, 10, 12, 15, 16으로 나누어떨어져 단을 나누기 좋았다. 네이선 스미스(Nathan Smith)가 2008년 무렵 공개한 960 Grid System은 이 폭을 12단(단 60, 거터 20) 또는 16단으로 나눈 틀을 내려받아 쓰게 했고, 비슷한 그리드 틀이 널리 퍼졌다. 2011년에 처음 공개된 부트스트랩(Bootstrap)도 12단 그리드를 기본으로 삼았다. 오늘날의 12단 관례는 이 흐름에서 이어진 것이다. 다만 그때의 그리드는 판의 폭이 고정이라는 전제 위에 있었다. 휴대폰에서 960 픽셀 판을 열면 화면이 통째로 줄어들어 글자가 깨알처럼 보인다. 책방 E의 옛 사이트가 바로 그 상태다.

카드를 단에 걸치기

그리드 위의 요소는 단 하나에 들어가는 경우보다 여러 단에 걸치는 경우가 많다. CSS 그리드에서는 부모에 단을 선언하고, 자식에게 몇 단에 걸칠지 적는다.

.page {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));  /* 12단 */
  column-gap: 24px;                                   /* 거터 */
}
.feature { grid-column: span 8; }   /* 8단에 걸친다 */
.aside   { grid-column: span 4; }
.card    { grid-column: span 4; }   /* 한 줄에 세 장 */

그리드의 힘은 줄과 줄 사이에서 나온다. 한 줄만 보면 카드 세 장을 아무 폭으로나 나란히 놓아도 그럴듯하다. 하지만 위 줄의 "추천 도서"(8단)와 "이달의 행사"(4단) 아래에 카드를 놓으면, 카드의 경계가 위 줄의 경계와 맞는지가 드러난다. 아래에서 카드 사이의 경계를 끌어 본다. "단 선에 붙이기"를 끄면 경계가 아무 데나 놓인다.

SIMULATOR

단 선에 맞춰 카드 나누기

카드 사이 손잡이를 끈다
카드마다 걸친 단—
카드 폭(px)—
단 선에서 벗어난 경계—
해볼 것: ① 3장에서 카드 경계가 위 줄의 "추천 도서"와 "이달의 행사" 경계와 맞는지 본다. 첫 카드가 4단, 위의 추천 도서가 8단이라 둘째 카드의 오른쪽 끝이 위 줄의 경계와 같은 선에 선다. ② 손잡이를 끌어 첫 카드를 5단으로 넓힌다. 붙이기가 켜져 있으면 경계가 다음 단 선으로 뛰어간다. 4 · 4 · 4가 5 · 3 · 4가 되어도 모든 경계는 여전히 단 선 위에 있다. ③ "단 선에 붙이기"를 끄고 경계를 조금씩 옮긴다. 카드 폭이 282.4처럼 소수가 되고, 경계가 위 줄의 선과 몇 픽셀 어긋난다. 단을 끄고 보면 더 거슬린다. ④ 4장이나 6장으로 바꿔 본다. 12의 약수라 모두 남는 단 없이 나누어진다. (콘텐츠 폭 1,200, 12단, 거터 24, 단 폭 78. 카드 최소 폭은 40으로 막았다.)

경계를 붙이지 않고 끈 카드는 그 줄만 보면 문제가 없다. 하지만 "단 보기"를 끄고 화면 전체를 보면, 위 줄과 아래 줄의 경계가 몇 픽셀씩 어긋난 것이 눈에 걸린다. CompositionBook 12장의 "아슬아슬한 어긋남"과 같은 현상이다. 단 선은 첫 줄을 위해서가 아니라 둘째 줄을 위해 있다. 줄이 많아질수록, 페이지가 여러 장일수록 그 값이 커진다.

그리드는 시안 위에 깔아 두고 그린다 디자인 도구 대부분에는 화면 위에 단과 거터를 반투명 띠로 깔아 두는 기능(레이아웃 그리드, 열 안내선)이 있다. 그리드를 정했으면 모든 시안에 같은 설정을 깔고 그린다. 개발자 쪽도 마찬가지다. 브라우저 개발자 도구는 CSS 그리드 상자 위에 단 번호와 선을 겹쳐 보여 주는 기능을 갖고 있다. 시안의 안내선과 브라우저의 그리드 표시가 같은 자리에 서는지 나란히 놓고 보면, "몇 픽셀 어긋났다"는 대화가 "몇째 단에서 시작하느냐"는 대화로 바뀐다.

fr: 남는 공간을 나누는 단위

앞의 코드에서 12단은 repeat(12, minmax(0, 1fr))로 적었다. fr는 CSS 그리드가 도입한 비율 단위(Flexible length, fr)다. 고정된 칸과 거터를 먼저 빼고 남는 공간을 fr의 비로 나눈다. 200px 1fr 2fr라면 200픽셀 칸 하나를 먼저 놓고, 남는 공간을 1:2로 나눠 둘째와 셋째 칸에 준다.

$$1\,\text{fr} = \frac{W - \sum(\text{고정 칸}) - (\text{칸 수} - 1)\,g}{\sum(\text{fr 값})}$$
\(W\): 그리드 상자의 폭, \(g\): 칸 사이 간격(column-gap). 예: \(W = 800\), 200px 1fr 2fr, \(g = 24\)이면 \(1\,\text{fr} = (800 - 200 - 48)/3 = 184\). 칸 폭은 200, 184, 368. 단, 어떤 칸의 내용이 그보다 좁아질 수 없으면 그 칸을 먼저 떼어 내고 다시 나눈다.

식 아래의 "단,"이 중요하다. 1fr는 사실 minmax(auto, 1fr)의 줄임이다. 칸의 최솟값이 "내용이 더 줄어들 수 없는 폭"이라서, 줄바꿈되지 않는 긴 낱말이나 주소가 들어 있으면 그 칸이 다른 칸보다 넓어진다. 아래에서 화면 폭과 칸 정의를 바꿔 본다.

SIMULATOR

fr이 나누는 남는 공간

남는 공간—
1fr—
세 칸의 폭—
해볼 것: ① 200px 1fr 2fr에서 폭을 줄였다 늘린다. 200픽셀 칸은 그대로이고 둘째와 셋째 칸만 1:2를 지키며 늘고 준다. 폭 320에서는 남는 공간이 72뿐이라 fr 칸이 쪼그라든다. ② "1fr 1fr 1fr"는 세 칸이 늘 같다. ③ "1fr × 3 + 긴 주소"로 바꾸고 폭을 줄인다. 가운데 칸에 줄바꿈되지 않는 주소가 들어 있어, 어느 폭 아래에서는 가운데 칸이 더 줄지 않고 양옆 칸만 줄어든다. 세 칸이 같아야 할 그리드가 뒤틀린다. ④ "minmax(0,1fr)"로 바꾼다. 세 칸이 다시 같아지는 대신, 긴 주소가 칸 밖으로 넘쳐 옆 칸을 침범한다(붉은 부분). 이 경우에는 주소를 줄바꿈하도록(overflow-wrap: anywhere) 따로 처리해야 한다. (칸 사이 간격 24. 주소의 폭은 이 그림의 글꼴로 잰 값이고, 그 밖의 칸 내용은 충분히 줄어든다고 가정했다.)

열 그리드에 repeat(12, 1fr) 대신 repeat(12, minmax(0, 1fr))를 쓰는 이유가 여기 있다. 단 하나에 들어간 긴 낱말 때문에 그리드 전체가 뒤틀리는 것보다, 단 폭은 지키고 넘치는 내용을 따로 다루는 편이 낫다. 반대로 내용에 맞춰 칸이 늘어나야 하는 표나 곁글이라면 auto나 1fr의 성질이 도움이 된다. fr은 "남는 공간을 나눈다"는 한 문장으로 기억하고, 칸의 최솟값을 무엇으로 둘지를 따로 정한다.

퍼센트로 나눈 단과 거터는 맞지 않는다 그리드를 퍼센트로 적던 시절에는 단 폭을 25%로 주고 거터를 margin으로 따로 주는 일이 흔했다. 그러면 25% × 4에 거터가 더해져 100%를 넘고, 마지막 단이 다음 줄로 떨어진다(3장의 box-sizing 사고와 같은 산술이다). 계산식으로 25%에서 거터 몫을 빼는 방법(calc(25% - 18px))도 있지만, 거터가 바뀔 때마다 식을 고쳐야 한다. fr과 gap은 이 산술을 브라우저에게 넘긴다. 거터를 먼저 빼고 남는 것을 나누니, 거터를 바꿔도 단의 합이 넘치지 않는다.

한 방향과 두 방향

CSS에는 상자를 나란히 놓는 도구가 둘 있다. 플렉스박스(Flexbox, CSS Flexible Box Layout)는 상자들을 한 방향(가로 또는 세로)으로 흘려 보내며, 남는 공간을 어떻게 나눌지와 줄 안에서 어떻게 정렬할지를 정한다. CSS 그리드(CSS Grid Layout)는 가로와 세로의 선을 두 방향으로 미리 긋고, 상자를 그 칸에 놓는다. 주요 브라우저가 CSS 그리드를 모두 지원하게 된 것은 2017년이다.

플렉스박스: 한 방향으로 흐른다 줄마다 따로 나눈다 마지막 줄의 두 상자가 넓게 늘어났다 그리드: 선을 먼저 긋는다 가로세로가 함께 맞는다 마지막 줄도 위 줄의 단을 따른다
그림 2. 흐름과 칸. 플렉스박스는 상자를 한 줄로 흘리다 넘치면 다음 줄로 넘기고, 줄마다 남는 공간을 따로 나눈다. 그래서 줄이 바뀌면 상자 폭이 달라질 수 있다. 그리드는 가로와 세로의 선을 먼저 그어 두므로 어느 줄의 상자든 같은 단에 선다. 둘 중 하나가 낫다기보다, 한 줄 안의 정렬이면 플렉스박스, 줄과 줄이 함께 맞아야 하면 그리드다.

차이는 줄이 넘어갈 때 드러난다. 아래는 책 일곱 권을 카드로 놓은 목록이다. 카드의 기본 폭은 200, 카드 사이는 24다. 세 가지 방식으로 놓고 그리드 상자의 폭을 바꾼다. 점선은 첫 줄 카드의 경계를 아래로 이어 그은 것이다.

SIMULATOR

마지막 줄이 말해 주는 것

한 줄의 카드 수—
카드 폭: 위 줄 · 마지막 줄—
세로 열 맞춤—
해볼 것: ① "플렉스(늘어남)"에서 폭 760이면 한 줄에 세 장, 마지막 줄에 한 장이 남는다. 그 한 장이 줄 전체로 늘어나 다른 카드의 세 배가 넘는 폭이 된다. 표지 그림이 혼자 거대해진다. ② "플렉스(고정 폭)"으로 바꾼다. 카드가 200으로 고정되어 마지막 줄도 같은 폭이지만, 줄 오른쪽에 빈자리가 남아 목록이 왼쪽으로 쏠린다. ③ "그리드(auto-fill)"로 바꾼다. 카드가 줄 폭을 고르게 나누면서도 마지막 줄은 위 줄과 같은 단에 선다. ④ 폭을 천천히 바꾸며 세 방식의 마지막 줄을 비교한다. 플렉스(늘어남)는 남는 카드 수에 따라 마지막 줄이 계속 모양을 바꾼다. (플렉스: flex: 1 1 200px 또는 0 0 200px, 그리드: repeat(auto-fill, minmax(200px, 1fr)), 간격은 모두 gap: 24px.)

마지막 줄의 거대한 카드는 플렉스박스의 버그가 아니다. 플렉스박스는 줄마다 남는 공간을 그 줄의 상자들에게 나눠 주라는 규칙을 충실히 따랐다. 줄과 줄 사이의 관계는 플렉스박스가 모르는 일이다. 그래서 단추 여러 개를 한 줄에 놓거나, 머리 띠 안에서 로고를 왼쪽에, 메뉴를 오른쪽에 두는 일은 플렉스박스가 잘하고, 카드 목록처럼 줄과 줄이 맞아야 하는 일은 그리드가 잘한다. 한 페이지에 둘을 섞어 쓰는 것이 보통이다. 페이지의 큰 짜임은 그리드로, 카드 안의 단추 줄은 플렉스박스로.

셋째 방식에서 쓴 repeat(auto-fill, minmax(200px, 1fr))은 "카드가 200보다 좁아지지 않는 한 한 줄에 최대한 많이 넣고, 남는 폭은 고르게 나눠라"는 뜻이다. 이 한 줄이 화면 폭마다 단 수를 따로 정하는 일을 상당 부분 대신한다. 카드 폭이 화면 폭에 따라 어떻게 바뀌는지 그래프로 보자. 가로축이 목록 상자의 폭, 세로축이 카드 한 장의 폭이다.

SIMULATOR

카드 최소 폭이 정하는 톱니

세로선을 끌어 폭을 고른다
고른 폭—
한 줄 카드 수 · 카드 폭—
카드 폭이 오가는 범위(2장 이상)—
해볼 것: ① 처음 상태(최소 폭 240, 간격 24)에서 그래프가 톱니 모양인 것을 본다. 목록이 넓어지면 카드도 넓어지다가, 한 장이 더 들어갈 자리가 생기는 순간 카드 수가 하나 늘며 폭이 뚝 떨어진다. ② 세로선을 1,200에 놓는다. 한 줄에 네 장, 카드 폭 282다. 앞 절의 12단에서 3단에 걸친 카드와 정확히 같은 폭이다. ③ 최소 폭을 220으로 내린다. 1,200에서 다섯 장이 되고, 다섯은 12의 약수가 아니라서 노란 구간(12단과 어긋나는 카드 수)에 들어간다. ④ 최소 폭을 300 이상으로 올린다. 1,200에서 세 장, 카드 폭 384로 4단에 걸친 폭과 같다. (그래프의 카드 폭 = (목록 폭 − (n − 1) × 간격) ÷ n, n = ⌊(목록 폭 + 간격) ÷ (최소 폭 + 간격)⌋. 목록 폭이 최소 폭보다 좁으면 카드가 넘친다.)

톱니의 이빨 하나하나가 "한 줄에 n장"인 구간이다. 카드 폭은 최소 폭 아래로는 내려가지 않고, 위로는 다음 카드가 들어오기 직전까지 커진다. 두 장 이상인 구간에서는 그 위쪽 끝이 "최소 폭의 1.5배 + 간격의 절반"을 넘지 않는다(최소 폭 240, 간격 24라면 372). 그래서 최소 폭 하나만 잘 고르면 어떤 화면 폭에서도 카드 폭이 일정한 범위 안에 머문다. 그리고 앞 절의 12단과 함께 쓰려면 한 가지를 더 확인한다. 한 줄의 카드 수가 12의 약수(1, 2, 3, 4, 6)이고 카드 사이가 거터와 같으면, auto-fill로 놓은 카드의 경계는 계산하지 않아도 12단의 단 선 위에 정확히 선다. 식으로 확인하면 카드 \(n\)장의 폭 \((C - (n-1)G)/n\)과 \(12/n\)단에 걸친 폭이 같은 값이 된다.

.book-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr));
  gap: 24px;
}
/* min(240px, 100%): 목록이 240보다 좁은 화면에서도 카드가 넘치지 않게 */
auto-fill과 auto-fit auto-fill 자리에 auto-fit을 쓸 수도 있다. 카드가 한 줄을 다 채울 만큼 많을 때는 둘이 같다. 카드가 적을 때 차이가 난다. 카드가 두 장뿐인 넓은 화면에서 auto-fill은 빈 칸을 남겨 두어 두 장이 원래 폭을 지키고, auto-fit은 빈 칸을 접어 두 장이 줄 전체로 늘어난다. 신간이 두세 권뿐인 주에 표지가 화면만 하게 커지는 것을 막으려면 auto-fill이 안전하다. 반대로 항목 수가 적고 늘 줄을 꽉 채워야 하는 곳(예: 탭 두세 개)에는 auto-fit이 맞다.

그리드를 깨는 요소

TypeBook 9장에서 격자를 깨는 이유는 강조이고, 깨는 것이 효과를 내려면 적어야 하고 분명해야 한다고 했다. 웹도 같다. 모든 것이 단 선에 맞는 화면에서 사진 한 장이 화면 끝에서 끝까지 펼쳐지면, 그것이 가장 먼저 눈에 든다. 이렇게 콘텐츠 폭을 넘어 화면 가장자리까지 채우는 요소를 인쇄의 도련에서 빌려 전체 폭 요소(Full-bleed)라고 부른다. 반대로 단 선에서 10픽셀쯤 비켜 선 사진은 깬 것이 아니라 어긋난 것으로 읽힌다.

아래는 행사 소개 글 한 편이다. 제목과 본문은 12단 중 가운데 8단(3~10단)에 있다. 그림 하나의 자리를 네 가지로 바꿔 본다.

SIMULATOR

깬 것과 어긋난 것

그림의 시작 · 끝—
본문과 공유하는 세로선—
읽히는 인상—
해볼 것: ① "본문 폭"에서 그림의 양 끝이 본문의 양 끝과 같은 선에 있다. 조용하고 질서 있다. ② "넓게(10단)"로 바꾼다. 그림이 본문보다 양쪽으로 한 단씩 나왔지만 여전히 단 선 위다. 그리드를 크게 쓴 것이다. ③ "화면 끝까지"로 바꾼다. 그림이 콘텐츠 폭을 벗어나 화면 가장자리까지 간다. 이 페이지에서 가장 먼저 보이는 것이 된다. 깬 것이 분명하다. ④ "조금 어긋남"으로 바꾼다. 그림이 오른쪽으로 10픽셀 밀렸을 뿐인데, 본문과 공유하던 세로선을 모두 잃는다. "단 보기"를 켜면 어긋남이 더 분명해진다. (가상 화면 폭 1,280, 콘텐츠 폭 1,200, 12단, 거터 24. 공유하는 세로선은 본문의 왼쪽 끝, 오른쪽 끝, 가운데 중 그림의 끝·가운데와 3픽셀 안에서 맞는 것을 센다.)

"화면 끝까지"와 "조금 어긋남"은 둘 다 그리드를 벗어났지만 인상은 정반대다. 앞의 것은 벗어난 정도가 커서 의도가 분명하고, 뒤의 것은 의도인지 실수인지 알 수 없다. 웹에서는 하나가 더 있다. 화면 폭이 바뀌어도 깬 것이 계속 깬 것으로 보여야 한다. 전체 폭 요소는 어떤 화면에서든 화면 끝까지 가니 의도가 유지되지만, "왼쪽으로 40픽셀 내밀기" 같은 깨기는 좁은 화면에서 화면 밖으로 잘려 나가기 쉽다.

CSS 그리드에서는 선에 이름을 붙여 전체 폭 요소를 간단히 만든다. 콘텐츠 영역 양옆에 늘었다 줄었다 하는 칸을 하나씩 두고, 보통 요소는 가운데 칸에, 전체 폭 요소는 양 끝 선 사이에 놓는다.

.article {
  display: grid;
  grid-template-columns:
    [full-start] minmax(16px, 1fr)
    [content-start] min(1200px, 100% - 32px) [content-end]
    minmax(16px, 1fr) [full-end];
}
.article > *      { grid-column: content; }   /* 기본: 콘텐츠 폭 */
.article > .bleed { grid-column: full; }      /* 깨는 요소: 화면 끝까지 */
그리드 안의 그리드: subgrid 카드 안의 제목, 설명, 가격이 이웃 카드의 제목, 설명, 가격과 같은 높이에 서게 하고 싶을 때가 있다. 카드마다 제목 길이가 달라 한 줄짜리와 두 줄짜리가 섞이면, 그 아래 가격의 높이가 카드마다 들쭉날쭉해진다. CSS Grid Layout Module Level 2의 subgrid는 자식 그리드가 부모 그리드의 선을 그대로 물려받게 해서 이 문제를 푼다. 카드들이 부모의 가로줄(제목 줄, 설명 줄, 가격 줄)을 함께 쓰는 것이다. 2023년 무렵에 주요 브라우저가 모두 지원하게 되었다. 종이 그리드의 기준선 격자(TypeBook 9장)가 이웃한 단의 글줄을 맞추던 것을 웹에서 카드 단위로 하는 셈이다.

신간 목록 짜기

이 장의 도구를 모아 책방 E의 신간 목록을 짠다. 위쪽에는 "이달의 책" 띠가 있고, 그 아래에 신간 여덟 권이 카드로 놓인다. "옛 사이트"는 지금 쓰는 960 픽셀 고정 템플릿(12단, 단 60, 거터 20)이다. "새 그리드"는 이 장에서 세운 규칙이다. 콘텐츠 폭은 최대 1,200, 양옆 바깥 여백은 최소 16, 12단에 거터 24, 카드는 최소 폭 이상으로 auto-fill. 화면 폭 슬라이더로 가상 브라우저를 늘이고 줄인다.

SIMULATOR

책방 E 신간 목록

콘텐츠 폭 · 단 폭—
한 줄 카드 수 · 카드 폭—
12단과 맞음—
문제—
해볼 것: ① "옛 사이트"에서 화면 폭을 1,600까지 늘린다. 960 판이 가운데에 섬처럼 떠 있고 양옆이 텅 빈다. ② 화면 폭을 360으로 줄인다. 판이 화면보다 넓어 오른쪽이 잘리고 가로 스크롤이 생긴다(실제 휴대폰은 대개 판 전체를 줄여서 보여 주므로 글자가 깨알처럼 작아진다). ③ "새 그리드"로 바꾸고 1,280에서 본다. 콘텐츠 폭 1,200, 단 폭 78, 한 줄에 네 권(282)이 3단씩 정확히 선다. "이달의 책" 띠만 화면 끝까지 가서 그리드를 깬다. ④ 화면 폭을 천천히 줄인다. 네 권, 세 권, 두 권, 한 권으로 바뀌는 동안 카드 경계가 계속 12단 선 위에 있다. 360에서는 단 폭이 5 남짓이라 12단 안내선은 의미를 잃지만 카드는 한 줄에 한 권으로 잘 선다. ⑤ 카드 최소 폭을 200이나 216으로 내린다. 1,280에서 다섯 권이 되어 12단과 어긋난다. (가상 화면이다. 카드 높이는 표지 비율 3:4와 글 세 줄로 계산했다. 좁은 화면의 단 수는 이 장에서 정하지 않았다.)

새 그리드에서 주목할 점은 화면 폭마다 따로 정한 숫자가 하나도 없다는 것이다. 콘텐츠 폭은 min(1200px, 100% - 32px) 한 식으로, 한 줄의 카드 수는 auto-fill과 최소 폭 240으로 정해진다. 화면이 바뀔 때 규칙이 알아서 계산한다. 그래도 남는 것이 있다. 좁은 화면에서 12단 안내선은 단 폭이 거터보다 좁아져 쓸모가 없고, 카드가 아닌 요소들(행사 일정의 날짜와 설명처럼 두 단으로 나란히 서야 하는 것)은 어느 폭에서 한 단으로 바뀌어야 하는지 아직 정하지 않았다. 그것은 다음 장의 일이다.

모눈종이 위의 12단 5 mm 모눈종이 한 장을 가로로 놓고, 가로 60칸(300 mm)을 콘텐츠 폭이라고 하자. 단 4칸, 거터 1칸으로 하면 12단 × 4칸 + 11 × 1칸 = 59칸이라 한 칸이 남는다. 양옆 바깥 여백을 반 칸씩 두면 맞아떨어진다. 색연필로 단을 칠한 뒤, 다른 종이에 신간 카드(3단 폭)와 행사 카드(4단 폭), 추천 도서 띠(8단 폭)를 오려 위에 놓아 본다. 두 줄, 세 줄로 놓을 때 경계가 맞는지 손으로 확인한다. 마지막으로 카드 하나를 반 칸만 비켜 놓고 한 걸음 물러나 본다. 반 칸이 얼마나 거슬리는지가 단 선의 값이다.

서윤의 웹 개편 노트

CASE 서윤웹 개편 노트 · 4장

네 권씩, 3단씩, 1,200에서 멈추기

2028년 1월 셋째 주. 주인의 바람은 하나였다. "새로 들어온 책을 한눈에 많이 보여 주고 싶어요." 서윤은 1장에서 정한 세 폭(360·768·1,280) 가운데 가장 넓은 1,280 시안부터 열었다. 옛 사이트의 신간 목록은 960 고정 판 위에 카드 네 장(단 60, 거터 20, 카드 220)이었다. 같은 짜임을 1,280 화면에 그대로 키우면 카드가 다섯 장, 여섯 장까지 들어갈 것 같았지만, 지후는 숫자부터 적자고 했다.

두 사람은 12단을 기본으로 놓고 거터를 16, 24, 32로 바꿔 가며 카드 세 장(4단씩)과 네 장(3단씩)을 나란히 그려 봤다. 거터 16은 표지와 표지가 붙어 보여 책이 한 줄의 띠처럼 읽혔고, 32는 카드 네 장일 때 표지가 작아졌다. 3장에서 정한 카드 사이 24가 거터로도 자연스러웠다. 거터와 카드 사이가 같으니 단 선과 카드 경계가 한 체계가 된다. 콘텐츠 폭은 1,200에서 멈추기로 했다. 그러면 단 폭이 정확히 78이 되고(12 × 78 + 11 × 24 = 1,200), 그보다 넓은 화면에서는 양옆이 고르게 비어 1,920 모니터에서도 표지가 거인처럼 커지지 않는다. 좁은 쪽은 3장의 휴대폰 양옆 여백 16을 바깥 여백의 최솟값으로 삼았다.

카드 수는 서윤이 화면 폭마다 정하려 했다. "1,280에서 네 장, 그 아래로는 세 장…" 지후가 이 장의 톱니 그래프를 보여 주며 말했다. "기기마다 정하지 말고 카드가 얼마나 좁아질 수 있는지만 정해요. 나머진 브라우저가 해요." 서윤은 표지와 두 줄짜리 제목(17px)이 버티는 가장 좁은 카드를 시안에서 찾았고, 그 값이 240이었다. 최소 폭 240이면 1,200에서 네 장(282), 그보다 좁아지면 세 장, 두 장, 한 장이 되는데, 모두 12의 약수라 카드 경계가 늘 12단 선 위에 선다. 220으로 내리면 1,200에서 다섯 장이 되어 단과 어긋났다. 그리드를 깨는 요소는 하나만 두었다. 신간 목록 위의 "이달의 책" 띠다. 책방 초록 바탕이 화면 끝까지 가고, 그 안의 글은 콘텐츠 폭의 왼쪽 단 선에 맞췄다.

도경이 휴대폰(글자 크기 최대)으로 첫 시험판을 열어 보더니 한 권씩 크게 나오는 목록이 오히려 좋다고 했다. 다만 서윤이 행사 일정 페이지를 같은 그리드에 올려 보니, 날짜와 설명을 나란히 놓은 두 단이 태블릿 근처의 폭에서 어색하게 눌렸다. 카드가 아닌 요소들은 auto-fill로 해결되지 않았다.

넓은 화면 그리드12단 · 거터 24 콘텐츠 폭최대 1200 · 단 78 바깥 여백최소 16 카드 최소 폭240
이 장에서 정한 것넓은 화면 12단, 거터 24px(3장의 카드 사이와 같은 값), 콘텐츠 최대 폭 1,200px(단 폭 78px), 바깥 여백 최소 16px: 콘텐츠 폭 = min(1200px, 100% - 32px), 단은 repeat(12, minmax(0, 1fr)). 신간 카드는 repeat(auto-fill, minmax(min(240px, 100%), 1fr)), gap 24px. 카드 최소 폭 240px로 1,200에서 한 줄 네 권(카드 282px = 3단), 좁아지면 3·2·1권(모두 12단 선 위). 카드가 두세 권뿐일 때 커지지 않도록 auto-fit이 아닌 auto-fill. 그리드를 깨는 요소는 "이달의 책" 띠 하나(전체 폭, 안의 글은 콘텐츠 왼쪽 선).
아직 남은 문제좁은 화면에서 12단은 단 폭이 거터보다 좁아 안내선 구실을 못 한다. 행사 일정 페이지의 날짜·설명 두 단이 768 근처에서 어색하게 눌린다. 화면 폭에 따라 짜임 자체가 바뀌어야 하는 지점과, 제목 크기가 화면에 따라 어떻게 바뀌어야 하는지는 아직 정하지 않았다.
다음 단계5장: 기기 이름이 아니라 콘텐츠가 깨지는 곳에서 짜임을 바꾼다.

핵심 정리

  1. 열 그리드는 화면을 단, 거터, 바깥 여백으로 나눈 규칙이다. 화면 폭을 모르기 때문에 숫자가 아니라 "무엇이 늘고 무엇이 고정되는가"로 정하고, 콘텐츠에는 최대 폭을 둔다.
  2. 단 폭 \(c = (V - 2M - (n-1)G)/n\), \(k\)단에 걸친 폭 \(kc + (k-1)G\). 12는 1, 2, 3, 4, 6, 12로 나누어떨어져 카드 수와 2:1, 3:1 나누기를 모두 받아 준다. 단 폭이 거터보다 좁아지면 그 그리드는 그 화면에 맞지 않는다.
  3. 단 선은 둘째 줄을 위해 있다. 요소의 경계를 단 선에 맞추면 줄과 줄, 페이지와 페이지가 같은 선을 나눠 갖는다. 몇 픽셀 어긋난 경계는 실수로 읽힌다.
  4. fr은 고정 칸과 간격을 뺀 남는 공간을 비로 나눈다. 1fr의 최솟값은 내용의 최소 폭이라 긴 낱말이 칸을 넓힌다. 열 그리드에는 minmax(0, 1fr)를 쓴다.
  5. 플렉스박스는 한 방향으로 흘리며 줄마다 공간을 나누고, 그리드는 두 방향의 선을 먼저 긋는다. 줄과 줄이 맞아야 하는 카드 목록은 repeat(auto-fill, minmax(최소 폭, 1fr)). 한 줄의 카드 수가 12의 약수이고 간격이 거터와 같으면 카드는 12단 선에 선다.
  6. 그리드를 깨는 요소는 적게, 분명하게. 화면 끝까지 가는 전체 폭 요소는 어느 화면에서나 의도가 보이고, 조금 비켜 선 요소는 실수처럼 보인다.

확인 퀴즈

1. 콘텐츠 폭 1,200px(바깥 여백 제외)을 12단, 거터 24px로 나눈다. 단 폭과 3단에 걸친 카드의 폭은?

거터는 단 사이에만 있으니 12단이면 11개다. (1,200 − 11 × 24) ÷ 12 = 936 ÷ 12 = 78. 3단 카드는 단 셋과 그 사이 거터 둘을 차지해 3 × 78 + 2 × 24 = 282다. 100은 거터를 빼지 않은 값이고, 76은 거터를 12개로 센 값, 228은 거터를 빼먹은 카드 폭이다.

2. 폭 800px인 그리드 상자에 grid-template-columns: 200px 1fr 2fr; column-gap: 24px;를 주었다(칸 내용은 충분히 줄어든다). 셋째 칸의 폭은?

남는 공간은 800 − 200 − 2 × 24 = 552다. 이것을 1:2로 나누면 1fr = 184, 2fr = 368이다. 400은 간격을 빼지 않은 값((800 − 200) ÷ 3 × 2), 533은 고정 칸까지 fr로 나눈 값(800 ÷ 3 × 2), 384는 간격을 하나만 뺀 값이다.

3. 책 일곱 권을 flex: 1 1 200px로 감싼(flex-wrap) 목록에서 한 줄에 세 권씩 놓였다. 마지막 줄의 한 권은 어떻게 되며, 이를 피하는 방법으로 알맞은 것은?

플렉스박스는 줄마다 남는 공간을 그 줄의 상자들에게 나눠 준다. 마지막 줄에 한 권만 있으면 그 한 권이 남는 공간을 모두 받아 늘어난다. 거터를 넓혀도 줄마다 따로 나누는 성질은 그대로다. 그리드는 가로세로 선을 먼저 그어 두므로 마지막 줄도 위 줄의 단에 선다.

4. 모든 요소가 12단에 맞춰진 행사 소개 페이지에서 그림 하나의 자리를 고른다. 그리드를 "의도적으로 깬" 것으로 가장 분명하게 읽히는 것은?

깨는 것이 강조가 되려면 적고 분명해야 한다. 화면 끝까지 가는 전체 폭 그림은 벗어난 정도가 커서 의도가 분명하다. 10px, 6px 어긋남은 깬 것이 아니라 실수처럼 보이는 어긋남이다. 8단에 정확히 맞춘 그림은 그리드를 지킨 것이다.

출처