B-5: Grid & 고급 레이아웃
안녕하세요, 홍순구 튜터입니다. 지난 시간에 우리는 Flexbox로 "한 줄 정렬"을 완전히 손에 넣었어요. 메뉴를 한 줄에 가지런히 펼치고, 로그인 폼을 화면 정중앙에 띄우고, 좋아요·댓글·공유 아이콘을 한 줄에 나란히 세웠죠. display: flex 한 줄이면 부모가 정렬 사령관이 된다는 것도 배웠고요.
그런데 지난 시간 끝에 솔직하게 털어놨던 숙제가 하나 있었어요. 인스타그램 프로필 페이지의 사진 격자예요. 사진이 가로 3개씩, 세로로 여러 줄, 바둑판처럼 쫙 깔려 있는 그 화면이요. "그럼 flex-wrap: wrap으로 만들면 되지 않나?" 싶어서 실제로 해보면, 얼추 비슷하게는 돼요. 하지만 화면 너비가 바뀌거나 사진 개수가 3의 배수가 아닐 때 마지막 줄이 어색하게 흐트러지고, 칸 너비가 미묘하게 안 맞아요. 왜 그럴까요?
Flexbox는 태생이 한 방향(1차원) 도구이기 때문이에요. "한 줄로 흘려보내는" 일에는 최고지만, 가로 칸과 세로 칸을 동시에 똑 떨어지게 맞추는 일은 잘하는 게 아니거든요. 줄바꿈을 시켜도 각 줄을 따로따로 계산할 뿐, "3열 × 여러 행"이라는 격자 전체를 한눈에 보고 칸을 맞추진 못해요. 격자에는 격자를 위해 태어난 도구가 따로 있어요. 오늘의 주인공, CSS Grid(그리드) 예요.
비유를 들어볼게요. Flexbox가 "한 줄로 세우는 줄 세우기"라면, Grid는 엑셀 표 또는 모눈종이예요. 먼저 "가로로 3칸, 세로로 3줄" 하고 표의 칸을 그어두면, 그 안에 내용물을 넣는 순간 알아서 칸칸이 자리를 잡아요. 한 방향이 아니라 가로와 세로, 두 방향(2차원)을 한 번에 다루는 거죠. 인스타그램 사진 격자처럼 행과 열이 또렷한 배치에는 Grid가 압도적으로 편해요.
Flexbox = 한 줄(1차원) Grid = 표·모눈종이(2차원)
[A][B][C][D][E] → ┌──┬──┬──┐
한 방향으로 흘려보냄 │A │B │C │
줄바꿈해도 줄마다 따로 계산 ├──┼──┼──┤
│D │E │F │ 가로·세로를
├──┼──┼──┤ 동시에 맞춤
│G │H │I │
└──┴──┴──┘
오늘은 그동안 한 번도 손대지 않았던 profile.html을 드디어 단장해요. CSS 파일 세 개를 연결하고, 게시물 사진을 3열 격자로 쫙 깔 거예요.
grid-template-columns로 칸을 긋고, fr이라는 새 단위로 칸 너비를 비율로 나누고, repeat으로 "똑같은 칸 3개"를 짧게 쓰고, minmax와 auto-fill/auto-fit으로 칸이 찌그러지지 않게 다듬어요.
마지막엔 grid-template-areas로 프로필 헤더(아바타·이름·통계·소개)를 이름표 붙이듯 배치하고, Grid와 Flexbox를 상황에 맞게 골라 쓰는 감각까지 잡습니다.
💡 오늘 수업의 핵심 — "부모 상자에 display: grid를 주고, grid-template-columns·repeat·fr로 칸을 그어, 인스타그램 프로필의 사진 3열 격자와 헤더 배치를 완성한다" 🎯
🎯 학습 목표
display: grid로 부모 상자를 grid 컨테이너로 만들고, Flexbox(1차원)와 Grid(2차원)의 차이를 구분합니다.grid-template-columns로 열(세로 칸)을 긋고, 게시물 사진을 3열 격자로 배치합니다.fr(fraction, 남은 공간의 비율) 단위로 칸 너비를 px·% 대신 비율로 나눕니다.repeat()으로 "똑같은 칸을 N개" 짧게 쓰고, grid 전용gap으로 칸 사이 간격을 벌립니다.- 사진이 늘어나면 행(가로 줄)이 저절로 생기는 묵시적 행(implicit rows)의 원리를 이해합니다.
minmax()로 칸의 최소·최대 크기를 정해, 칸이 너무 좁아지거나 넓어지지 않게 막습니다.auto-fill/auto-fit으로 화면 너비에 따라 칸 개수가 자동으로 바뀌는 격자를 맛봅니다.grid-template-areas로 영역에 이름을 붙여, 프로필 헤더의 아바타·이름·통계·소개를 한눈에 배치합니다.- 큰 격자는 Grid가, 칸 안의 한 줄 나열은 Flexbox가 맡는 "조합" 감각을 익혀, 둘을 상황에 맞게 골라 씁니다.
오늘도 외우려 하지 마세요. Grid는 "칸을 어떻게 그을지"만 정하면 내용물이 알아서 칸을 채우는 도구예요. grid-template-columns의 값을 1fr 1fr 1fr에서 2fr 1fr 1fr로 바꾸는 순간 첫 칸이 두 배로 넓어지는 걸 직접 보면, 표로 외우는 것보다 훨씬 빨리 손에 들어와요. 자, 모눈종이에 칸을 그으러 가볼까요?
Step 1: "display: grid 첫 등장 — profile.html을 단장하고 사진을 3열로"
오늘은 그동안 한 번도 손대지 않았던 페이지부터 열어요. 바로 profile.html이에요. 피드 페이지와 로그인 페이지는 지난 모듈들에서 CSS를 입혔는데, 프로필 페이지는 아직 맨몸이거든요. 가장 먼저 할 일은 우리가 만들어둔 CSS 파일 세 개를 연결하는 거예요.
<!-- instagram-clone-frontend/profile.html -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Instagram - 프로필</title>
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/layout.css">
<link rel="stylesheet" href="css/components.css">
</head>
이 세 줄을 넣는 순간 프로필 페이지도 바탕색·폰트(base.css), 고정 네비게이션 바(layout.css), 카드 생김새(components.css)를 한꺼번에 물려받아요. 다른 페이지와 똑같은 옷을 입는 거죠.
이제 오늘의 주인공인 게시물 격자를 봐요. 게시물 사진들을 <div class="post-grid">라는 부모 상자로 감싸뒀어요. 그 안에는 사진 <figure>가 9개 들어 있고요.
<section class="profile-posts">
<h3>내 게시물</h3>
<div class="post-grid">
<figure>
<img src="https://picsum.photos/seed/myinsta1/300/300"
alt="새벽 작업실 책상 위 키보드와 듀얼 모니터"
width="300" height="300" loading="lazy">
<figcaption>오늘의 작업실 #coding</figcaption>
</figure>
<!-- ... 같은 모양의 figure가 모두 9개 ... -->
</div>
</section>
자, 이 <div class="post-grid">에 Grid를 켜볼게요. Flexbox 때와 똑같이 부모 상자에 한 줄을 줍니다. 다만 이번엔 flex가 아니라 grid예요.
/* instagram-clone-frontend/css/layout.css */
.post-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
(여기서는 이해를 돕기 위해 1fr을 세 번 길게 썼어요. 우리 파일에는 이걸 더 짧은 repeat(3, 1fr)로 적어뒀는데, 그 축약은 Step 3에서 풀어볼게요. 지금은 길게 봐도 똑같이 동작해요.)
저장하는 순간 사진 9개가 가로 3개씩, 세로 3줄로 쫙 깔려요. 무슨 일이 벌어진 걸까요?
display: grid→.post-grid가 grid 컨테이너(격자를 지휘하는 부모 상자)가 돼요. 그 직계 자식인<figure>9개는 자동으로 grid 아이템(칸에 담기는 자식)이 되고요.grid-template-columns: 1fr 1fr 1fr→ "열(세로로 선 칸)을 3개 만들어라"는 뜻이에요. 값이 3개니까 열도 3개죠.
열이 3개인데 아이템은 9개예요. 그럼 첫 3개가 1행에 들어가고, 다음 3개가 2행, 마지막 3개가 3행으로 저절로 내려가요. 우리가 행 개수를 말해주지 않았는데도요.
grid-template-columns: 1fr 1fr 1fr (열 3개)
1열 2열 3열
┌──────┬──────┬──────┐
│ 사진1│ 사진2│ 사진3│ ← 1행
├──────┼──────┼──────┤
│ 사진4│ 사진5│ 사진6│ ← 2행 (저절로 생김)
├──────┼──────┼──────┤
│ 사진7│ 사진8│ 사진9│ ← 3행 (저절로 생김)
└──────┴──────┴──────┘
지난 시간 끝에 "flex-wrap으로 격자를 흉내 내면 마지막 줄이 흐트러진다"고 했던 거 기억하시죠? Grid는 처음부터 "열 3개"라는 격자 틀을 정해두기 때문에, 사진이 9개든 7개든 칸이 똑 떨어져요. 한 방향으로 흘려보내다 줄을 바꾸는 Flexbox와는 출발점이 다른 거예요.
🙋 직접 해보세요 — "열 개수를 바꾸면?"
프로필 페이지를 열고 개발자 도구(F12) Elements 탭에서 .post-grid를 클릭하세요. 오른쪽 Styles 패널에서 grid-template-columns의 값을 1fr 1fr(값 2개)로 바꿔보세요. 열이 2개로 줄면서 사진이 2개씩 다시 줄을 맞춰요. 값을 아예 지우면? 사진들이 한 줄에 하나씩 세로로 쌓여요(열이 1개가 되는 셈이죠). 값의 개수가 곧 열의 개수라는 걸 눈으로 확인해보세요.
💡 튜터의 결론: Grid도 Flexbox처럼 부모 상자에 display: grid를 주는 것에서 시작해요. 그다음 grid-template-columns에 값을 나열한 만큼 열이 생기고, 아이템이 그 열 수를 넘으면 행은 저절로 만들어져요. 그런데 방금 쓴 1fr은 대체 무슨 단위일까요? px도 %도 아닌 이 낯선 단위를 다음 Step에서 풀어봅니다.
Step 2: "fr 단위 — 남은 공간을 비율로 나눈다"
방금 grid-template-columns: 1fr 1fr 1fr라고 썼어요. 여기 나온 fr은 fraction(분수, 비율)의 줄임말로, Grid에서 처음 등장하는 새 단위예요. 뜻은 딱 하나, "남은 공간을 이 숫자의 비율로 나눠 가져라"예요.
격자 부모의 너비에서 칸 사이 간격을 뺀 나머지 공간을 생각해보세요. 1fr 1fr 1fr은 그 공간을 1 : 1 : 1로 나누라는 뜻이라, 세 칸이 똑같은 너비가 돼요. 만약 첫 칸만 넓히고 싶으면 숫자를 바꾸면 돼요.
/* 첫 칸을 2배로 넓히고 싶다면 */
.post-grid {
grid-template-columns: 2fr 1fr 1fr;
}
이러면 남은 공간을 2 : 1 : 1, 총 4등분해서 첫 칸이 2/4(절반), 나머지 두 칸이 각각 1/4를 가져요.
1fr 1fr 1fr 2fr 1fr 1fr
┌────┬────┬────┐ ┌────────┬────┬────┐
│ 1 │ 2 │ 3 │ │ 1 │ 2 │ 3 │
└────┴────┴────┘ └────────┴────┴────┘
똑같이 1:1:1 첫 칸이 2배 (2:1:1)
px나 %로도 칸을 만들 수는 있어요. 하지만 둘 다 불편한 구석이 있어요.
200px 200px 200px→ 화면이 좁아져도 칸이 200px 그대로라 화면 밖으로 삐져나가요.33.3% 33.3% 33.3%→ 칸 사이 간격(gap)까지 더해지면 합이 100%를 넘어 마지막 칸이 다음 줄로 밀려요. 계산이 까다롭죠.
반면 fr은 간격을 먼저 빼고 남은 공간만 비율로 나눠요. 그래서 gap이 있어도 칸이 절대 삐져나가지 않아요. 격자에서 fr이 사랑받는 이유예요.
🙋 직접 해보세요 — "비율 숫자를 바꿔보기"
.post-grid의 grid-template-columns를 3fr 1fr 1fr로 바꿔보세요. 첫 칸이 확 넓어지고 나머지가 좁아져요. 1fr 2fr 1fr로 하면 가운데 칸만 넓어지고요. 숫자는 꼭 1, 2 같은 정수가 아니어도 돼요. 1.5fr 1fr 1fr도 됩니다. 숫자의 비율 그대로 칸이 나뉘는 걸 확인해보세요.
(실험이 끝나면 다시 1fr 1fr 1fr로 돌려두세요. 인스타그램 격자는 똑같은 칸이 어울리니까요.)
💡 튜터의 결론: fr은 "남은 공간을 비율로 나누는" 단위예요. 1fr 1fr 1fr은 세 칸을 똑같이, 2fr 1fr 1fr은 첫 칸을 2배로 만들죠. gap을 먼저 빼고 나눠 주기 때문에 칸이 삐져나갈 걱정이 없어요. 그런데 1fr을 세 번 쓰는 건 좀 귀찮지 않나요? 똑같은 칸 100개를 만든다고 생각해보세요. 다음 Step에서 이 반복을 한 방에 줄이는 repeat()을 배웁니다.
Step 3: "repeat()과 gap — 똑같은 칸은 짧게, 사이는 벌리고"
1fr 1fr 1fr처럼 똑같은 값을 여러 번 쓰는 건 칸이 3개라 그나마 괜찮지만, 만약 12개라면 1fr을 열두 번 써야 해요. 너무 길죠. 그래서 Grid는 **repeat(반복 횟수, 반복할 패턴)**이라는 축약 문법을 줘요.
repeat(3, 1fr)은 "1fr을 3번 반복하라"는 뜻이라 1fr 1fr 1fr과 완전히 똑같아요. 우리 파일에는 이 짧은 형태로 적어뒀어요. 여기에 칸 사이 간격을 주는 gap까지 더한 게 진짜 게시물 격자의 모습이에요.
/* instagram-clone-frontend/css/layout.css */
.post-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
}
/* 격자 칸 안의 사진은 칸 너비를 꽉 채우기 */
.post-grid figure {
margin: 0;
}
.post-grid img {
width: 100%;
height: auto;
display: block;
}
.post-grid figcaption {
padding: 0.25rem;
color: #8e8e8e;
font-size: 0.8rem;
}
하나씩 짚어볼게요.
grid-template-columns: repeat(3, 1fr)→ "1fr칸을 3번" = 똑같은 3열.repeat(3, 1fr)이1fr 1fr 1fr과 같다는 걸 꼭 기억하세요.gap: 0.5rem→ 칸과 칸 사이를 0.5rem씩 벌려요. Flexbox에서 배운 그gap이 Grid에서도 똑같이 동작해요. 다만 Grid에서는 가로 간격과 세로 간격을 한 번에 잡아줘요..post-grid img { width: 100% }→ 사진이 자기 칸의 너비를 꽉 채우게 해요. 칸이 좁아지면 사진도 따라 작아지죠.figure의 기본 여백(margin)은 0으로 없애서 칸에 딱 맞게 붙였고요.
gap은 가로·세로를 따로 주고 싶을 때 두 값을 쓸 수도 있어요. gap: 0.5rem 1rem이면 행 사이(세로)는 0.5rem, 열 사이(가로)는 1rem이에요. 첫 값이 행 간격, 둘째 값이 열 간격이죠. 한 값만 쓰면 가로·세로 똑같이 적용돼요.
repeat(3, 1fr) = 1fr 1fr 1fr gap: 0.5rem
┌────┐ ┌────┐ ┌────┐
│ 1 │ │ 2 │ │ 3 │ ← 칸 사이가 0.5rem씩 벌어짐
└────┘ └────┘ └────┘
↕ 0.5rem (행 사이도 동일)
┌────┐ ┌────┐ ┌────┐
│ 4 │ │ 5 │ │ 6 │
└────┘ └────┘ └────┘
🙋 직접 해보세요 — "repeat 횟수와 gap 바꿔보기"
grid-template-columns를 repeat(4, 1fr)로 바꾸면 열이 4개가 되면서 사진이 4개씩 줄을 맞춰요. repeat(2, 1fr)이면 2개씩이고요. 이번엔 gap을 0으로 줘보세요. 사진들이 빈틈없이 딱 붙어요(예전 인스타그램 격자가 이런 모습이었죠). gap: 1.5rem으로 키우면 사이가 시원하게 벌어지고요. repeat의 숫자와 gap 값만 바꿔도 격자 느낌이 확 달라지는 걸 확인해보세요.
💡 튜터의 결론: repeat(3, 1fr)은 1fr 1fr 1fr의 짧은 표현이에요. 칸이 많아질수록 repeat의 위력이 커지죠. gap은 Flexbox에서처럼 칸 사이를 벌리되, Grid에서는 가로·세로를 한 번에(또는 두 값으로 따로) 잡아줘요. 그런데 우리는 열만 3개라고 정했지, 행은 몇 개라고 말한 적이 없어요. 사진 9개가 어떻게 3행으로 알아서 나뉘었을까요? 다음 Step에서 행의 비밀을 풀어봅니다.
Step 4: "묵시적 행 — 행은 말 안 해도 저절로 생긴다"
다시 보면 신기해요. 우리는 grid-template-columns로 열만 3개라고 정했어요. 행이 몇 개인지는 한마디도 안 했죠. 그런데 사진 9개가 정확히 3행으로 나뉘었어요. 어떻게 된 걸까요?
Grid는 이렇게 동작해요. 명시적으로 정한 열(3개)에 아이템을 왼쪽부터 채우다가, 한 행이 꽉 차면 자동으로 새 행을 만들어 다음 아이템을 내려보내요. 이렇게 우리가 정해주지 않았는데 Grid가 알아서 만든 행을 묵시적 행(implicit rows), 우리말로 "암묵적으로 생긴 행"이라고 불러요.
grid-template-columns: repeat(3, 1fr) ← 우리가 정한 건 "열 3개"뿐
1열 2열 3열
┌────┬────┬────┐
│ 1 │ 2 │ 3 │ ← 1행: 3칸 다 차면
├────┼────┼────┤
│ 4 │ 5 │ 6 │ ← Grid가 2행을 "저절로" 만들어 이어 채움
├────┼────┼────┤
│ 7 │ 8 │ 9 │ ← 3행도 마찬가지 (묵시적 행)
└────┴────┴────┘
그럼 이 묵시적 행의 높이는 누가 정할까요? 따로 정해주지 않으면 칸 안의 내용물(사진) 높이에 맞춰져요. 우리 사진은 정사각형(300×300)이라 칸도 정사각형에 가깝게 잡히죠. 만약 모든 행의 높이를 똑같이 고정하고 싶으면 grid-auto-rows를 쓰면 돼요.
/* 묵시적으로 생기는 행의 높이를 모두 10rem으로 고정하고 싶다면 */
.post-grid {
grid-auto-rows: 10rem;
}
grid-auto-rows는 "앞으로 저절로 생길 행들은 이 높이로 만들어라"는 뜻이에요. 행 높이를 일정하게 맞춰 바둑판처럼 반듯한 격자를 만들 때 써요. (우리 파일에서는 사진 비율을 그대로 살리려고 따로 고정하지 않았어요. 사진이 정사각형이라 그대로도 충분히 반듯하거든요.)
참고로 열을 미리 정하듯 행도 grid-template-rows로 미리 정할 수 있어요. 이렇게 미리 정한 행은 명시적 행, 넘쳐서 저절로 생긴 행은 묵시적 행이에요. 게시물처럼 개수가 늘었다 줄었다 하는 격자는 행을 일일이 정하기보다 묵시적 행에 맡기는 게 훨씬 편해요. 사진을 3개 더 올리면 4행이 알아서 생기니까요.
🙋 직접 해보세요 — "사진을 늘리면 행도 늘어날까?"
profile.html에서 <figure> 하나를 통째로 복사해 .post-grid 안에 붙여 사진을 10개, 11개로 늘려보세요. 열은 그대로 3개인데 행이 4행으로 알아서 늘어나요. 우리가 행 개수를 건드리지 않았는데도요. 이게 묵시적 행의 편리함이에요. 반대로 grid-auto-rows: 6rem을 줘보면 모든 행이 납작하게 6rem 높이로 고정되는 것도 확인해보세요.
💡 튜터의 결론: 열만 정해두면 행은 아이템 개수에 따라 Grid가 저절로 만들어요. 이게 묵시적 행이고, 높이는 grid-auto-rows로 조절할 수 있어요. 덕분에 게시물이 몇 개든 똑같은 3열 격자가 유지되죠. 그런데 화면이 아주 좁아지면 칸도 한없이 좁아져서 사진이 우표만 해질 수 있어요. 칸이 너무 작아지지 않게 막는 방법, 다음 Step의 minmax()예요.
Step 5: "minmax() — 칸이 너무 좁아지지도, 넓어지지도 않게"
1fr은 "남은 공간을 비율로 나눠라"라서, 화면이 좁아지면 칸도 같이 한없이 좁아져요. 폰처럼 작은 화면에서 3열을 고집하면 사진이 우표만 하게 쪼그라들죠. 반대로 아주 큰 화면에선 칸이 너무 넓어져 사진이 흐릿하게 늘어날 수도 있고요.
이럴 때 칸의 최소·최대 크기를 정해주는 게 minmax(최솟값, 최댓값)이에요. 이름 그대로 minimum(최소)과 maximum(최대)을 한 번에 정하는 함수죠.
/* 칸은 최소 8rem, 최대는 남은 공간 비율(1fr)까지 */
.post-grid {
grid-template-columns: repeat(3, minmax(8rem, 1fr));
}
minmax(8rem, 1fr)은 "칸이 아무리 좁아져도 8rem 밑으로는 안 내려가고, 공간이 남으면 1fr 비율로 늘어나라"는 뜻이에요. 화면이 좁아지면 칸이 8rem에서 멈추고, 그래도 공간이 부족하면 가로 스크롤이 생기거나(혹은 다음 Step의 자동 칸 조절로 넘어가요).
repeat(3, 1fr) repeat(3, minmax(8rem, 1fr))
좁은 화면에서 좁은 화면에서
┌─┬─┬─┐ ┌────┬────┬────┐
│▪│▪│▪│ ← 우표만큼 작아짐 │ 8rem│ 8rem│ 8rem│ ← 최소 8rem에서 버팀
└─┴─┴─┘ └────┴────┴────┘
여기서 실무 팁 하나. 1fr 대신 minmax(0, 1fr)을 쓰는 경우도 많아요. 1fr에는 사실 "내용물보다는 작아지지 않는다"는 숨은 최소 크기가 있어서, 칸 안에 아주 긴 글이나 큰 사진이 들어가면 칸이 비율을 깨고 삐져나갈 때가 있거든요. minmax(0, 1fr)은 그 최소 크기를 0으로 풀어, 칸이 비율을 정확히 지키게 해줘요. 큰 내용물이 격자를 밀어내는 사고를 막는 안전장치죠.
⚠️
minmax는 우리 게시물 격자의 최종본에는 넣지 않았어요. 사진이 정사각형이라repeat(3, 1fr)만으로 충분히 반듯하거든요. 하지만 화면 크기에 따라 칸을 유연하게 다뤄야 할 때minmax는 거의 항상 등장해요. 바로 다음 Step에서 진가를 발휘합니다.
🙋 직접 해보세요 — "최솟값을 키워보기"
개발자 도구에서 .post-grid의 grid-template-columns를 repeat(3, minmax(8rem, 1fr))로 바꾼 뒤, 브라우저 창 너비를 좁게 줄여보세요(창 가장자리를 드래그). 칸이 8rem까지만 좁아지고 더는 안 줄어들어요. 최솟값을 12rem으로 키우면 더 일찍 버티고요.
칸이 무작정 작아지지 않게 바닥을 깔아주는 게 minmax의 역할이라는 걸 느껴보세요. (실험 후 repeat(3, 1fr)로 돌려두세요.)
💡 튜터의 결론: minmax(최소, 최대)는 칸이 너무 좁아지거나 넓어지지 않게 양쪽에 울타리를 쳐요. minmax(0, 1fr)은 비율을 정확히 지키게 하는 안전장치고요. 그런데 화면이 좁아질 때 칸 크기만 버티는 게 아니라, 칸 개수 자체를 줄이면 어떨까요? "넓으면 5칸, 좁으면 2칸"처럼요. 다음 Step의 auto-fill/auto-fit이 바로 그걸 해줍니다.
Step 6: "auto-fill/auto-fit — 칸 개수를 화면에 맡긴다"
지금까지는 우리가 "열은 3개"라고 못 박았어요. 그런데 생각해보면, 넓은 모니터에서는 5열도 괜찮고 좁은 폰에서는 2열이 더 나을 수 있잖아요? 화면 너비에 따라 칸 개수를 Grid가 알아서 정하게 하는 방법이 있어요. repeat()의 첫 인자에 숫자 대신 auto-fill 또는 auto-fit을 넣는 거예요.
/* 칸은 최소 8rem, 들어갈 수 있는 만큼 자동으로 채워라 */
.post-grid {
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
repeat(auto-fill, minmax(8rem, 1fr))을 풀어 읽으면 "최소 8rem짜리 칸을, 한 줄에 들어갈 수 있는 만큼 자동으로 만들어라"예요. 화면이 넓으면 칸이 많이, 좁으면 적게 생겨요. 우리가 개수를 안 정해도 화면 너비가 정해주는 거죠. 여기서 minmax가 빛을 발해요. "최소 크기"가 있어야 Grid가 "이 너비면 칸 몇 개가 들어가겠다"를 계산할 수 있거든요.
repeat(auto-fill, minmax(8rem, 1fr))
넓은 화면: ┌──┬──┬──┬──┬──┐ 칸 5개 자동
└──┴──┴──┴──┴──┘
중간 화면: ┌──┬──┬──┐ 칸 3개 자동
└──┴──┴──┘
좁은 화면: ┌──┬──┐ 칸 2개 자동
└──┴──┘
그럼 auto-fill과 auto-fit은 뭐가 다를까요? 칸을 다 채우고도 줄에 빈 공간이 남았을 때 차이가 나요.
auto-fill: 빈 칸(눈에 안 보이는 빈 트랙)을 그대로 남겨둬요. 아이템은 왼쪽에 모이고 오른쪽에 빈 자리가 생기죠.auto-fit: 빈 칸을 접어 없애고, 남은 공간을 실제 아이템들이 나눠 가져요. 그래서 칸이 옆으로 쭉 늘어나 줄을 꽉 채워요.
아이템이 줄을 꽉 채울 만큼 많으면 둘은 똑같이 보여요. 차이는 "아이템이 적어 빈 자리가 생길 때"만 드러나죠. 보통 사진 격자가 화면을 꽉 채우길 원하면 auto-fit을 더 자주 써요.
⚠️ 이건 반응형(화면 크기에 따라 레이아웃이 바뀌는 것)의 맛보기예요.
auto-fit만으로도 칸 개수가 화면에 반응하긴 하지만, "폰에서는 1열, 태블릿은 2열, 데스크톱은 3열"처럼 화면 구간마다 정교하게 나누는 건 다음 시간에 배울 미디어 쿼리(Media Query)의 몫이에요. 그래서 우리 파일은 지금은 깔끔한repeat(3, 1fr)3열 고정으로 두고, 본격적인 반응형은 다음 모듈에서 완성할 거예요.
🙋 직접 해보세요 — "창 너비를 줄이며 칸 개수 보기"
.post-grid를 grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr))로 바꾼 뒤, 브라우저 창 너비를 천천히 줄였다 늘였다 해보세요. 칸 개수가 5 → 4 → 3 → 2개로 화면에 맞춰 저절로 바뀌어요.
이번엔 auto-fit을 auto-fill로 바꾸고, 사진을 2~3개만 남겨보세요(나머지 figure를 잠깐 주석 처리). auto-fill은 오른쪽에 빈 자리를 남기고, auto-fit은 칸을 늘려 줄을 꽉 채우는 차이가 보일 거예요.
💡 튜터의 결론: repeat(auto-fill, ...)과 repeat(auto-fit, ...)은 칸 개수를 화면 너비에 맡겨요. minmax로 최소 크기를 정해줘야 동작하고, 빈 자리가 생길 때 auto-fill은 남기고 auto-fit은 접어 채우는 차이가 있죠. 여기까지가 "사진 격자"의 모든 것이에요.
이제 시선을 위로 올려, 프로필 페이지 맨 위의 헤더(아바타·이름·통계·소개)를 Grid로 배치해볼게요. 새로운 무기, grid-template-areas를 들고요.
Step 7: "grid-template-areas — 영역에 이름표를 붙여 배치한다"
이제 프로필 페이지 맨 위 헤더를 봐요. 인스타그램 프로필 상단은 보통 왼쪽에 동그란 아바타, 오른쪽에 이름·통계·소개가 세 줄로 쌓인 모양이에요. 이런 배치는 Grid의 또 다른 기능인 grid-template-areas로 아주 직관적으로 짤 수 있어요. 영역마다 이름표를 붙이고, 그 이름표를 그림 그리듯 배치하는 방식이에요.
먼저 마크업이에요. 헤더 안에 아바타·이름·통계·소개 네 조각이 들어 있어요.
<!-- instagram-clone-frontend/profile.html -->
<section class="profile-header">
<img class="profile-avatar"
src="https://picsum.photos/seed/avatar1/150/150"
alt="홍순구님의 프로필 사진" width="150" height="150">
<h2 class="profile-name">홍순구</h2>
<ul class="profile-stats">
<li><strong>42</strong> 게시물</li>
<li><strong>8,500</strong> 팔로워</li>
<li><strong>312</strong> 팔로잉</li>
</ul>
<p class="profile-bio">@hong_tutor · 풀스택 개발자 / 강의 진행 중</p>
</section>
이제 이 네 조각을 Grid로 배치해요.
/* instagram-clone-frontend/css/layout.css */
.profile-header {
display: grid;
grid-template-columns: auto 1fr;
grid-template-areas:
"avatar name"
"avatar stats"
"avatar bio";
align-items: center;
gap: 0.5rem 1.5rem;
max-width: 32rem;
margin: 2.5rem auto 1.5rem;
}
.profile-avatar { grid-area: avatar; border-radius: 50%; }
.profile-name { grid-area: name; margin: 0; }
.profile-stats { grid-area: stats; }
.profile-bio { grid-area: bio; margin: 0; }
핵심은 두 단계예요.
먼저 부모(.profile-header)에서 grid-template-areas로 격자 모양을 글자로 그려요. 큰따옴표 한 줄이 격자의 한 행이고, 그 안의 단어가 각 칸의 이름표예요. 위 그림을 읽으면 이런 격자가 돼요.
grid-template-columns: auto 1fr
("avatar" 칸은 내용 너비 auto, 오른쪽은 1fr로 남은 공간 전부)
avatar 열 name/stats/bio 열 (1fr)
┌──────────┬─────────────────────────────┐
1행 │ │ 홍순구 (name) │
│ ╭────╮ ├─────────────────────────────┤
2행 │ │아바타│ │ 42 · 8,500 · 312 (stats) │
│ ╰────╯ ├─────────────────────────────┤
3행 │ │ @hong_tutor ... (bio) │
└──────────┴─────────────────────────────┘
↑ "avatar"가 3행에 걸쳐 한 칸으로 합쳐짐
"avatar"라는 이름이 세 줄에 똑같이 적혀 있죠? 같은 이름이 이어지면 Grid가 그 칸들을 하나로 합쳐줘요. 그래서 아바타가 왼쪽에서 세 줄 높이를 통째로 차지하고, 오른쪽엔 이름·통계·소개가 차곡차곡 쌓여요.
그다음 각 자식에서 grid-area로 "나는 이 이름표 칸에 들어갈게"라고 지정해요. .profile-avatar { grid-area: avatar }는 "아바타 이미지는 avatar 칸으로"라는 뜻이죠. 이름표와 자식을 연결하는 거예요.
나머지 줄도 짚어볼게요.
grid-template-columns: auto 1fr→ 왼쪽 avatar 열은 내용물(150px 아바타) 너비만큼auto, 오른쪽 열은 남은 공간 전부1fr.align-items: center→ 각 행에서 내용을 세로 가운데로 맞춰요.max-width: 32rem; margin: 2.5rem auto 1.5rem→ 헤더를 게시물 격자와 똑같은 32rem 폭으로 가운데 정렬하고(페이지 본문 폭 통일), 위쪽 여백 2.5rem으로 고정 네비게이션 바에 가리지 않게 내려줬어요.
오늘 헤더를 손보면서 고정 네비게이션 바도 살짝 다듬었어요. 로고
<h1>의 기본 위아래 여백 때문에 바가 필요 이상으로 두꺼웠거든요.body > header h1 { margin: 0 }한 줄로 군더더기 여백을 없애 바를 슬림하게 만들었어요.
🙋 직접 해보세요 — "이름표를 옮겨보기"
grid-template-areas의 순서를 바꿔보세요. 예를 들어 "name avatar", "stats avatar", "bio avatar"로 적으면 아바타가 오른쪽으로 가고 글이 왼쪽으로 옮겨가요. 코드의 칸 그림을 바꾸면 화면 배치가 그대로 따라오는 게 보이죠? grid-template-areas의 가장 큰 장점은 이렇게 CSS가 곧 레이아웃의 그림이라 한눈에 읽힌다는 거예요.
💡 튜터의 결론: grid-template-areas는 격자 칸에 이름표를 붙이고, 자식에서 grid-area로 "나는 이 칸"이라고 연결하는 방식이에요. 같은 이름을 이어 적으면 칸이 합쳐지죠. CSS만 봐도 화면 모양이 그려져서 복잡한 배치를 직관적으로 짤 수 있어요. 그런데 통계 줄을 보면 "게시물·팔로워·팔로잉"이 한 줄에 나란히 있어요.
이건 격자가 아니라 한 줄 정렬이잖아요? 그럼 여기엔 누가 어울릴까요? 다음 Step에서 Grid와 Flexbox를 함께 씁니다.
Step 8: "Grid + Flexbox 조합 — 큰 배치는 Grid, 한 줄은 Flexbox"
방금 만든 헤더에서 통계 부분을 다시 봐요. "게시물 42 · 팔로워 8,500 · 팔로잉 312" 이 세 개가 한 줄에 나란히 놓여야 해요. 그런데 이건 행과 열이 있는 격자가 아니라, 한 방향으로 흐르는 한 줄 정렬이죠. 어디서 많이 본 일이에요. 맞아요, 지난 시간 배운 Flexbox가 가장 잘하는 일이에요.
그래서 우리는 두 도구를 함께 써요. 헤더 전체의 큰 배치(아바타·이름·통계·소개 네 영역)는 Grid가 맡고, 통계 안의 세 항목을 한 줄로 늘어놓는 일은 Flexbox가 맡는 거죠.
/* instagram-clone-frontend/css/layout.css */
.profile-stats {
display: flex;
gap: 1.5rem;
margin: 0;
padding: 0;
list-style: none;
}
여기서 재밌는 점이 있어요. .profile-stats는 두 가지 역할을 동시에 해요.
- 부모(
.profile-header) 입장에서 보면,.profile-stats는 grid의 한 칸(stats 영역)에 담긴 grid 아이템이에요. - 그런데 자기 자신은
display: flex를 켜서, 자기 자식인<li>세 개에게는 flex 컨테이너가 돼요.
즉 하나의 요소가 "위에서는 격자의 칸"이면서 "안에서는 한 줄 정렬의 사령관"인 거예요. 이렇게 Grid 칸 안에 Flexbox를 넣어 쓰는 조합은 실무에서 정말 자주 나와요.
.profile-header = Grid (큰 배치)
┌──────────┬─────────────────────────────────┐
│ │ 홍순구 │
│ 아바타 │ [42 게시물][8,500 팔로워][312 팔로잉]│ ← 이 칸 안은 Flexbox
│ │ @hong_tutor ... │
└──────────┴─────────────────────────────────┘
└─ stats 칸: display:flex로 세 항목 한 줄 ─┘
그럼 언제 Grid, 언제 Flexbox일까요? 간단한 기준이 있어요.
- 2차원(행과 열을 동시에 맞춤)이면 Grid — 사진 격자, 페이지 전체 골격, 헤더의 영역 배치.
- 1차원(한 줄/한 방향으로 흐름)이면 Flexbox — 메뉴 한 줄, 통계 한 줄, 좋아요·댓글 아이콘 줄.
둘은 경쟁 관계가 아니라 짝꿍이에요. 큰 틀을 Grid로 잡고, 그 안의 한 줄짜리 부분을 Flexbox로 채우는 게 가장 흔한 실무 패턴이에요.
🙋 직접 해보세요 — "flex를 끄면 통계가 어떻게 될까?"
개발자 도구에서 .profile-stats의 display: flex를 꺼보세요(체크박스 해제). 통계 세 개가 한 줄에서 다시 세로로 쌓여요(<li>의 본능이죠). 다시 켜면 한 줄로 모이고요. 헤더 전체(Grid)는 그대로인데 통계 칸 안(Flexbox)만 바뀌는 걸 보면, 두 도구가 각자 자기 영역을 맡고 있다는 게 분명히 보여요. gap 값을 바꿔 항목 사이 간격도 조절해보세요.
💡 튜터의 결론: 큰 배치는 Grid(2차원), 그 안의 한 줄 정렬은 Flexbox(1차원). 한 요소가 부모에겐 grid 아이템이면서 자기 자식에겐 flex 컨테이너가 될 수 있어요. 둘을 골라 쓰고 겹쳐 쓰는 감각이 레이아웃의 핵심이에요. 이걸로 프로필 페이지가 완성됐어요. 오늘 배운 걸 정리해볼까요?
마무리
오늘은 CSS 레이아웃의 마지막 큰 무기, CSS Grid를 손에 넣었어요. 그동안 맨몸이던 profile.html을 단장하고, 게시물 사진을 반듯한 3열 격자로 깔고, 헤더까지 이름표로 배치해 진짜 인스타그램 프로필처럼 만들었죠.
오늘 배운 것
display: grid→ 부모를 grid 컨테이너로. 직계 자식이 grid 아이템.grid-template-columns→ 열을 긋는다. 값의 개수가 열의 개수.fr→ 남은 공간을 비율로 나누는 단위.gap을 빼고 나눠 삐져나가지 않음.repeat(N, 패턴)→ 똑같은 칸을 짧게.repeat(3, 1fr)=1fr 1fr 1fr.gap→ 칸 사이 간격. 가로·세로 한 번에(또는 두 값으로 따로).- 묵시적 행 → 열만 정하면 행은 아이템 개수대로 저절로.
grid-auto-rows로 높이 조절. minmax(최소, 최대)→ 칸이 너무 좁아지거나 넓어지지 않게.auto-fill/auto-fit→ 칸 개수를 화면 너비에 맡김.grid-template-areas+grid-area→ 영역에 이름표를 붙여 직관적으로 배치.- Grid + Flexbox 조합 → 큰 배치는 Grid, 한 줄은 Flexbox.
Flexbox와 Grid, 언제 무엇을?
지난 시간과 오늘을 한 표로 정리하면 이래요. 둘의 경계가 또렷해질 거예요.
| 상황 | 어울리는 도구 | 예시 |
|---|---|---|
| 한 줄/한 방향으로 흐름 (1차원) | Flexbox | 메뉴 한 줄, 통계 한 줄, 좋아요·댓글 아이콘 |
| 행과 열을 동시에 맞춤 (2차원) | Grid | 사진 격자, 헤더 영역 배치, 페이지 골격 |
| 큰 틀 + 그 안의 한 줄 | Grid + Flexbox | Grid 칸 안에 flex로 항목 정렬 |
외울 필요 없어요. "한 줄이면 Flexbox, 격자면 Grid, 큰 틀 잡고 안을 한 줄로 채우면 둘 다"만 기억하면 돼요.
다음 시간 예고
오늘 우리 격자는 항상 3열 고정이에요. 그런데 폰처럼 좁은 화면에서 3열을 고집하면 사진이 우표만큼 작아져서 답답하죠. Step 6에서 auto-fit으로 칸 개수가 화면에 반응하는 맛은 봤지만, "폰에서는 1열, 태블릿은 2열, 데스크톱은 3열"처럼 화면 구간마다 레이아웃을 정교하게 갈아끼우진 못했어요.
다음 시간엔 화면 크기에 따라 디자인을 바꾸는 반응형 웹(Responsive Web)을 배웁니다. 화면 너비를 조건으로 거는 미디어 쿼리(@media), 요소가 담긴 부모 크기에 반응하는 컨테이너 쿼리(@container), 그리고 화면에 맞는 이미지를 골라 보내는 <picture>·srcset까지요.
오늘 만든 3열 격자를 폰에서는 1열, 태블릿에서는 2열, 데스크톱에서는 3열로 갈아끼우면서, 모든 화면에서 예쁜 인스타그램을 완성할 거예요.
오늘 격자를 손에 넣었으니, 다음 시간엔 그 격자를 화면 크기에 맞춰 자유자재로 바꿔봅시다!
과제
오늘 배운 Grid를 직접 손에 익혀볼 차례예요. 기초 → 응용 → 탐구 순서로 풀어보세요. 모든 과제는 지금까지 배운 HTML과 CSS만으로 충분히 해낼 수 있어요.
[구현] 탐색 페이지 4열 사진 격자 (기초)
인스타그램 탐색(Explore) 화면은 프로필보다 사진을 더 빽빽하게, 보통 가로 4개씩 보여줘요. profile.html의 게시물 격자를 참고해, 사진을 4열로 까는 격자를 만들어보세요.
<div class="explore-grid">안에<figure>(사진)를 8개 이상 넣으세요..explore-grid에display: grid와grid-template-columns를 주되, 열을 4개로 만드세요.repeat()을 꼭 활용하세요.- 칸 사이 간격(
gap)을2px처럼 아주 좁게 줘서, 사진이 빽빽하게 붙은 탐색 화면 느낌을 내보세요. - 사진이 칸 너비를 꽉 채우도록
img에width: 100%를 주는 것도 잊지 마세요.
[구현] 화면에 반응하는 사진 격자 (응용)
위에서 만든 격자(또는 프로필 게시물 격자)를, 화면 너비에 따라 칸 개수가 저절로 바뀌는 격자로 바꿔보세요.
grid-template-columns를repeat(auto-fit, minmax(?, 1fr))형태로 바꾸세요.?자리에 칸의 최소 너비(예:10rem)를 직접 정해보세요.- 브라우저 창 너비를 천천히 줄였다 늘였다 하면서, 칸 개수가 어떻게 변하는지 관찰하세요.
- 최소 너비를
8rem/12rem/16rem으로 바꿔가며, 같은 화면에서 칸 개수가 어떻게 달라지는지 비교해보세요.minmax의 최솟값이 칸 개수를 어떻게 좌우하는지 몸으로 느껴보는 게 목표예요.
[탐구] grid-template-areas로 나만의 카드 레이아웃
grid-template-areas를 써서, 게시물 카드 하나의 헤더 부분을 직접 배치해보세요. 예를 들어 왼쪽에 아바타, 오른쪽 위에 작성자 이름, 오른쪽 아래에 게시 시간을 두는 레이아웃을요.
- 어떤 이름표(
"avatar name","avatar time"같은)를 어떻게 배치할지 먼저 종이에 격자 그림을 그려보세요. - 그 그림을
grid-template-areas로 옮기고, 각 자식에grid-area로 이름을 연결하세요. - 아바타가 두 줄 높이를 차지하게 하려면 이름표를 어떻게 적어야 할까요? 직접 시도하고, 왜 그렇게 동작하는지 한두 문장으로 정리해보세요.
생각해볼 주제
정답을 적는 문제가 아니에요. 오늘 배운 도구의 "왜"를 곱씹어보는 질문들이에요. 스스로 답을 만들어본 뒤, 예시답안과 비교해보세요.
1. Flexbox와 Grid, 애매할 땐 무엇을 기준으로 고를까?
"한 줄이면 Flexbox, 격자면 Grid"라고 배웠어요. 그런데 실무에는 애매한 경우가 많아요. 예를 들어 똑같이 생긴 카드 여러 개를 가로로 나열하는 화면은 Flexbox로도, Grid로도 만들 수 있어요. 이렇게 둘 다 가능할 때, 여러분이라면 무엇을 기준으로 도구를 고르겠어요? "내용물의 개수가 정해져 있는가", "칸의 크기를 똑같이 맞춰야 하는가" 같은 관점에서 생각해보세요.
2. 격자에서 왜 px나 %보다 fr이 편할까?
칸 너비는 200px로도, 33%로도, 1fr로도 만들 수 있어요. 그런데 gap(칸 사이 간격)이 함께 있을 때 %로 칸을 나누면 계산이 까다로워지고, px는 화면이 좁아질 때 삐져나가요. fr은 이 두 문제에서 자유롭죠. fr이 "남은 공간을 나눈다"는 정의가 왜 gap과 잘 어울리는지, 셋의 차이를 곱씹어보세요.
3. auto-fit으로도 칸이 화면에 반응하는데, 왜 굳이 미디어 쿼리가 필요할까?
repeat(auto-fit, minmax(10rem, 1fr))만으로도 화면이 좁아지면 칸 개수가 줄어요. 일종의 반응형이죠. 그런데도 다음 시간에 미디어 쿼리를 따로 배우는 이유가 뭘까요? auto-fit이 바꿀 수 있는 건 "칸 개수"뿐이라는 점에서 출발해보세요. 폰에서는 글자 크기나 헤더 배치 자체를 바꾸고 싶다면, auto-fit만으로 될까요?
✅ 예시 답안정답 보기
과제와 생각해볼 주제의 예시답안이에요. 정답이 하나만 있는 건 아니에요. 칸 개수나 간격 값은 취향대로 골라도 좋아요. 중요한 건 부모에
display: grid를 주고,grid-template-columns·repeat·fr로 칸을 의도대로 그었는가 예요.
🎯 [과제 1 예시답안] 탐색 페이지 4열 사진 격자
핵심 접근
이 과제는 Step 1~3에서 만든 게시물 3열 격자를 4열로 바꿔 다른 곳에 재사용하는 연습이에요. 새 기술이 아니라 "부모에 display: grid, grid-template-columns: repeat(4, 1fr), gap으로 간격, img에 width: 100%"라는 같은 패턴을 열 개수만 바꿔 옮기는 거죠. 탐색 화면 느낌을 살리려면 gap을 아주 좁게 주는 게 포인트예요.
예시 구현
먼저 사진을 담을 격자 마크업을 추가해요(explore.html을 새로 만들거나, 연습용으로 profile.html에 잠깐 넣어봐도 좋아요).
<div class="explore-grid">
<figure>
<img src="https://picsum.photos/seed/ex1/300/300" alt="탐색 사진 1"
width="300" height="300" loading="lazy">
</figure>
<!-- ... 같은 모양의 figure를 8개 이상 ... -->
</div>
그리고 layout.css에 4열 격자 규칙을 추가해요.
/* instagram-clone-frontend/css/layout.css 에 추가 */
/* ===== 탐색 페이지 4열 사진 격자 ===== */
.explore-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2px;
}
.explore-grid img {
width: 100%;
height: auto;
display: block;
}
repeat(4, 1fr)로 똑같은 칸 4개를 만들고, gap: 2px로 사진을 바짝 붙였어요. img에 width: 100%를 줘서 사진이 칸을 꽉 채우게 했고요. 게시물 격자와 달라진 건 열 개수(3 → 4)와 간격(0.5rem → 2px)뿐이에요. Grid의 패턴을 한 번 익히면 이렇게 어디든 바로 옮길 수 있어요.
채점 포인트
| 항목 | 확인 내용 |
|---|---|
| grid 컨테이너 | 격자 부모에 display: grid를 줬는가 |
| 4열 + repeat | grid-template-columns: repeat(4, 1fr)로 열을 4개 만들었는가 |
| 좁은 간격 | gap을 좁게 줘서 빽빽한 탐색 화면 느낌을 냈는가 |
| 사진 채우기 | img에 width: 100%를 줘서 칸을 채웠는가 |
흔한 실수
grid-template-columns를 빠뜨림 —display: grid만 주고 열을 정하지 않으면, 칸이 1열로만 쌓여요. 열 개수는grid-template-columns가 정한다는 걸 기억하세요.1fr대신300px로 고정 — 사진 원본이 300px이라300px을 써버리면, 화면이 좁을 때 격자가 화면 밖으로 삐져나가요. 비율 단위1fr을 써야 화면에 맞게 줄었다 늘었다 해요.
🎯 [과제 2 예시답안] 화면에 반응하는 사진 격자
핵심 접근
열 개수를 고정하지 않고 화면 너비가 정하게 만드는 과제예요. repeat()의 첫 인자에 숫자 대신 auto-fit을, 둘째 인자에 minmax()를 넣는 게 핵심이에요. minmax의 최솟값이 "칸이 이만큼은 돼야 한다"는 기준이 되고, 그 기준으로 Grid가 "이 화면엔 칸 몇 개가 들어가겠다"를 계산해요.
예시 구현
/* instagram-clone-frontend/css/layout.css 에 추가(또는 .post-grid를 잠깐 수정) */
.explore-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
gap: 0.5rem;
}
repeat(auto-fit, minmax(10rem, 1fr))을 풀어 읽으면 "최소 10rem짜리 칸을, 한 줄에 들어갈 수 있는 만큼 자동으로 만들어라"예요. 브라우저 창을 넓히면 칸이 5개, 6개로 늘고, 좁히면 3개, 2개로 줄어요. 우리가 열 개수를 정하지 않았는데 화면이 정해주는 거죠.
최솟값을 바꿔가며 관찰한 결과를 정리하면 이래요.
최솟값(minmax의 첫 값) |
같은 넓은 화면에서 칸 개수 | 느낌 |
|---|---|---|
8rem |
많이 (예: 6~7개) | 칸이 작고 빽빽함 |
12rem |
보통 (예: 4~5개) | 균형 잡힘 |
16rem |
적게 (예: 3개) | 칸이 크고 시원함 |
최솟값이 작을수록 칸이 더 많이 들어가요. "한 칸이 최소 얼마여야 하는가"가 곧 "한 줄에 몇 칸"을 좌우하는 거죠.
채점 포인트
| 항목 | 확인 내용 |
|---|---|
| auto-fit | repeat(auto-fit, ...)으로 칸 개수를 화면에 맡겼는가 |
| minmax | 둘째 인자에 minmax(최소, 1fr)을 줘서 최소 너비를 정했는가 |
| 관찰 | 창 너비를 바꾸며 칸 개수가 변하는 걸 직접 확인했는가 |
| 최솟값 비교 | 최솟값을 바꿔가며 칸 개수가 어떻게 달라지는지 비교했는가 |
흔한 실수
minmax없이repeat(auto-fit, 1fr)만 씀 —auto-fit은 "칸이 최소 얼마"인지 알아야 개수를 계산할 수 있어요.1fr만 주면 최소 기준이 없어 칸이 1개로만 잡혀요.auto-fit과minmax는 짝꿍이에요.auto-fit과auto-fill을 혼동 — 아이템이 줄을 꽉 채울 땐 둘이 똑같아 보여요. 차이는 빈 자리가 남을 때만 드러나니(fit은 칸을 늘려 채우고, fill은 빈 칸을 남김), 사진이 적을 때 둘을 바꿔보면 차이가 보여요.
🎯 [과제 3 예시답안] grid-template-areas로 나만의 카드 레이아웃
핵심 접근
Step 7의 프로필 헤더 배치를 게시물 카드 헤더에 응용하는 과제예요. 먼저 격자 그림을 종이에 그리고, 그 그림을 grid-template-areas로 옮긴 뒤, 각 자식에 grid-area로 이름을 연결하는 3단계예요. 아바타를 두 줄 높이로 만들려면 같은 이름을 두 줄에 적으면 된다는 걸 떠올리는 게 핵심이에요.
예시 구현
종이에 그린 격자 그림이 이렇다고 해볼게요.
┌────────┬──────────────┐
│ │ 작성자 이름 │ ← name
│ 아바타 ├──────────────┤
│ │ 3시간 전 │ ← time
└────────┴──────────────┘
avatar가 두 줄 높이
이걸 마크업과 CSS로 옮기면 이래요.
<div class="card-header">
<img class="ch-avatar" src="https://picsum.photos/seed/u1/48/48" alt="jaehoon 프로필"
width="48" height="48">
<span class="ch-name">jaehoon</span>
<span class="ch-time">3시간 전</span>
</div>
/* instagram-clone-frontend/css/layout.css 에 추가 */
.card-header {
display: grid;
grid-template-columns: auto 1fr;
grid-template-areas:
"avatar name"
"avatar time";
align-items: center;
gap: 0 0.75rem;
}
.ch-avatar { grid-area: avatar; border-radius: 50%; }
.ch-name { grid-area: name; font-weight: bold; }
.ch-time { grid-area: time; color: #8e8e8e; font-size: 0.85rem; }
grid-template-areas에서 "avatar"를 두 줄에 똑같이 적었기 때문에, 아바타가 두 줄 높이를 통째로 차지해요. 오른쪽엔 이름과 시간이 위아래로 쌓이고요. 같은 이름을 이어 적으면 그 칸들이 하나로 합쳐진다는 Step 7의 원리 그대로예요.
채점 포인트
| 항목 | 확인 내용 |
|---|---|
| 격자 그림 | 배치를 먼저 격자 그림으로 그려봤는가 |
| areas 정의 | grid-template-areas로 칸에 이름표를 붙였는가 |
| grid-area 연결 | 각 자식에 grid-area로 이름을 연결했는가 |
| 칸 합치기 | 같은 이름을 여러 줄에 적어 한 칸을 두 줄 높이로 합쳤는가 |
흔한 실수
- 이름표 개수와 열 개수가 안 맞음 —
grid-template-columns는 열 2개인데grid-template-areas한 줄에 이름을 3개 적으면, 격자가 깨져요. 한 줄의 이름 개수 = 열 개수여야 해요. grid-area연결을 빠뜨림 —grid-template-areas로 칸 이름만 짓고 자식에grid-area를 안 주면, 자식이 어느 칸인지 몰라 그냥 순서대로 채워져요. 이름표(부모)와 연결(자식)은 한 쌍이에요.
💭 [생각해볼 주제 예시답안]
1. Flexbox와 Grid, 애매할 땐 무엇을 기준으로 고를까?
"한 줄이면 Flexbox, 격자면 Grid"가 큰 원칙이지만, 카드 여러 개를 가로로 나열하는 것처럼 둘 다 가능한 경우엔 두 가지를 기준으로 보면 돼요.
첫째, 칸 크기를 똑같이 맞춰야 하는가예요. 사진 격자처럼 모든 칸이 정확히 같은 너비여야 하면 Grid가 편해요. repeat(3, 1fr) 한 줄로 똑 떨어지거든요. Flexbox로 같은 너비를 맞추려면 각 아이템에 flex-basis를 계산해 줘야 해서 손이 더 가요.
둘째, 개수가 유동적이고 한 줄로 흐르는가예요. 해시태그 칩처럼 개수가 들쭉날쭉하고 내용 길이에 따라 크기가 달라지며 자연스럽게 다음 줄로 흘러야 하면 Flexbox(flex-wrap)가 편해요. 칸을 딱 맞추는 게 아니라 "내용 크기대로 흘리는" 일이니까요.
정리하면, "칸을 격자로 똑 맞춘다 → Grid", "내용 크기대로 한 줄로 흘린다 → Flexbox"예요. 둘 다 돼도 이 기준으로 고르면 코드가 더 짧고 깔끔해져요.
🎯 면접관을 홀리는 핵심 멘트
"둘 다 가능할 땐 '칸을 똑같이 맞춰야 하는가'를 봐요. 사진 격자처럼 균일한 칸이 필요하면 Grid의
repeat(N, 1fr)이 한 줄로 끝나고, 해시태그처럼 내용 크기대로 흘려야 하면 Flexbox의flex-wrap이 자연스러워요. Grid는 '틀을 먼저 그리는' 방식, Flexbox는 '내용을 흘리는' 방식이라는 출발점 차이로 고릅니다."
2. 격자에서 왜 px나 %보다 fr이 편할까?
세 단위의 차이는 gap(칸 사이 간격)과 함께 쓸 때 가장 또렷해져요.
px는 고정값이라, 200px 200px 200px로 칸을 만들면 화면이 좁아져도 칸이 안 줄어 화면 밖으로 삐져나가요. %는 부모 너비 기준이라 그나마 낫지만, 33.3%를 세 개 쓰면 합이 100%인데 거기에 gap까지 더해져서 한 줄을 넘쳐버려요. 그래서 %로 정확히 나누려면 calc(33.3% - gap) 같은 계산을 해야 해요. 번거롭죠.
fr은 정의 자체가 **"gap을 먼저 빼고 남은 공간을 비율로 나눈다"**예요. 그래서 gap이 아무리 커져도 칸이 절대 삐져나가지 않아요. 계산도 필요 없고요. repeat(3, 1fr)이면 간격을 뺀 나머지를 알아서 1:1:1로 나눠 주거든요. 격자에서 fr이 사실상 표준처럼 쓰이는 이유예요.
🎯 면접관을 홀리는 핵심 멘트
"
fr은gap을 먼저 제외하고 남은 공간을 비율로 나누는 단위예요.%는gap까지 합쳐져 100%를 넘기기 쉬워calc()보정이 필요하고,px는 화면이 좁아질 때 삐져나가요.fr은 이 두 문제에서 자유로워서, 간격이 있는 격자에서는fr이 거의 정답입니다."
3. auto-fit으로도 칸이 화면에 반응하는데, 왜 굳이 미디어 쿼리가 필요할까?
repeat(auto-fit, minmax(10rem, 1fr))이 바꿀 수 있는 건 딱 하나, 칸 개수예요. 화면이 좁아지면 칸을 줄이고 넓어지면 늘리죠. 사진 격자처럼 "내용은 그대로, 개수만 조절"하면 되는 경우엔 이것만으로 충분해요.
하지만 진짜 반응형은 칸 개수만 바꾸는 게 아니에요. 폰에서는 헤더의 아바타를 더 작게, 글자 크기를 줄이고, 통계를 세로로 쌓고, 어떤 요소는 아예 숨기고 싶을 수 있어요. 이렇게 레이아웃과 스타일 자체를 화면 구간마다 다르게 바꾸는 건 auto-fit이 할 수 없는 일이에요. auto-fit은 격자의 칸 개수만 알 뿐, "지금 화면이 폰이냐 데스크톱이냐"를 조건으로 분기하진 못하거든요.
그래서 다음 시간에 배울 미디어 쿼리(@media)가 필요해요. "화면 너비가 768px 이하면 이렇게, 그 이상이면 저렇게"처럼 화면 구간을 조건으로 걸어 무엇이든 바꿀 수 있죠. auto-fit은 격자 한정의 똑똑한 자동 조절이고, 미디어 쿼리는 페이지 전체를 화면에 맞춰 갈아끼우는 더 큰 도구라고 보면 돼요. 둘은 경쟁이 아니라 함께 써요.
🎯 면접관을 홀리는 핵심 멘트
"
auto-fit은 격자의 '칸 개수'만 화면에 맞춰요. 반면 미디어 쿼리는 폰·태블릿·데스크톱 구간마다 글자 크기, 요소 배치, 표시 여부까지 무엇이든 바꿀 수 있죠. 그래서 사진 격자의 칸 수 조절은auto-fit으로 간단히, 페이지 전체 레이아웃 전환은 미디어 쿼리로 — 둘을 함께 씁니다."