B-3: 레이아웃 기초
안녕하세요, 홍순구 튜터입니다. 지난 시간에 우리는 화면을 보는 눈을 하나 바꿨어요. 모든 요소를 상자로 보고, 그 네 겹(content·padding·border·margin)을 직접 만져서 휑하던 로그인 페이지를 정갈한 흰 카드로 단장하고 base.css를 완성했죠. margin: ... auto로 폼을 화면 가로 가운데로 옮기기까지 했고요.
그런데 지난 시간 끝에 솔직하게 말씀드렸어요. 배치는 아직 절반만 했다고요. margin auto는 폼을 가로로만 옮겼을 뿐이고, 진짜 인스타그램을 떠올려보면 할 일이 더 남아 있어요. 맨 위 메뉴 바는 스크롤을 아무리 내려도 화면 꼭대기에 딱 붙어 따라오고, 게시물들은 가지런히 세로로 흐르고, 사진 위에는 작은 라벨이 얹혀 있죠. 이런 건 박스 모델의 여백 조절만으로는 만들 수 없어요. 상자의 "크기와 여백"이 아니라, 상자를 어디에 놓고 어떻게 흐르게 할지를 정하는 새로운 도구가 필요하거든요.
오늘 그 도구를 손에 넣습니다. 핵심은 딱 두 가지예요. 첫째는 display — 상자가 화면에서 한 줄을 통째로 차지할지, 아니면 글자처럼 옆으로 나란히 흐를지를 정하는 속성이에요. 둘째는 position — 상자를 원래 흐름에서 떼어내 내가 원하는 위치에 고정하거나 살짝 옮기는 속성이고요. 여기에 상자들이 겹쳤을 때 누가 위로 올라올지 정하는 z-index까지 더하면, 우리가 아는 그 인스타그램 화면 배치가 만들어져요.
비유를 하나 들어볼게요. 책상 위에 물건을 정리한다고 생각해봐요. display는 "이 물건이 한 줄을 통째로 차지하게 둘까, 아니면 다른 물건과 어깨를 나란히 놓을까"를 정하는 거예요. position은 "이 메모지를 흐름에 맡겨둘까, 아니면 압정으로 벽에 딱 박아 고정할까"를 정하는 거고요. 오늘은 이 두 결정을 CSS로 내리는 법을 배웁니다.
그리고 오늘부터 새 파일을 하나 시작해요. 지난 시간 완성한 base.css는 색·여백·글자 같은 "기본 단장"을 맡았다면, 오늘 만들 layout.css는 "배치"만 전담합니다. 단장과 배치를 파일로 나눠두면 나중에 찾고 고치기가 훨씬 편해져요.
💡 오늘 수업의 핵심 — "display로 상자의 흐름을, position으로 상자의 위치를 정해서 인스타그램의 고정 네비게이션 바를 완성한다" 🎯
🎯 학습 목표
display의 세 값(block·inline·inline-block)과display: none으로 요소가 화면을 어떻게 차지하는지 결정합니다.position의 다섯 값(static·relative·absolute·fixed·sticky)으로 요소를 원래 흐름에서 떼어 원하는 위치에 놓습니다.position: fixed로 스크롤해도 따라오는 상단 네비게이션 바를 만들고, 본문이 가려지는 흔한 함정을padding-top으로 해결합니다.position: absolute로 게시물 사진 위에 "NEW" 뱃지를 얹습니다(가장 가까운relative조상 기준).position: sticky로 스크롤하다 화면 위에 딱 붙어 멈추는 섹션 제목을 만듭니다.z-index와 쌓임 맥락(stacking context)으로 상자가 겹칠 때 누가 위로 올라올지 정합니다.float이 왜 한때 레이아웃의 주역이었다가 지금은 물러났는지 이해합니다.- 배치를 전담하는
layout.css를 새로 만들어base.css와 역할을 나눕니다.
오늘도 외우려 하지 마세요. 이 과목의 장점인 "한 줄 바꾸고 저장하면 화면이 바로 변한다"가 배치에서도 그대로예요. position: fixed 한 줄로 메뉴 바가 화면에 딱 붙는 순간을 직접 보면, 말로 백 번 듣는 것보다 한 번에 이해돼요. 자, 상자를 어디에 놓을지 정하러 가볼까요?
Step 1: "display ① — 한 줄을 통째로 쓰는 상자, 글자처럼 흐르는 상자"
배치의 첫 단추는 display예요. 모든 요소는 화면을 차지하는 방식이 둘 중 하나로 정해져 있어요. 하나는 블록(block), 다른 하나는 인라인(inline)이에요.
블록 요소는 한 줄을 통째로 차지해요. 옆에 빈 공간이 아무리 많아도 다른 요소를 못 들어오게 막고, 혼자 한 줄을 다 쓴 다음 다음 요소를 아래로 밀어내요. 그래서 블록 요소들은 위에서 아래로 차곡차곡 쌓여요. 지금까지 우리가 본 <h2> 제목, <p> 문단, <article> 게시물, <li> 목록 항목이 전부 블록이에요.
인라인 요소는 글자처럼 흘러요. 한 줄을 독차지하지 않고, 자기 내용 너비만큼만 차지한 뒤 옆에 다른 인라인 요소가 이어 붙어요. 문장 속 단어들이 줄바꿈 없이 이어지듯이요. <a> 링크, <strong> 강조, <span>이 인라인이에요.
[ block 요소 ] [ inline 요소 ]
┌──────────────────────────┐ 문장 속에서 [링크]와 [강조]가
│ <h2> 한 줄 통째로 차지 │ 줄바꿈 없이 옆으로 이어 흐른다
└──────────────────────────┘
┌──────────────────────────┐ ← 자기 내용만큼만 너비를 쓰고
│ <p> 역시 한 줄 통째로 │ 다음 요소가 옆에 붙는다
└──────────────────────────┘
┌──────────────────────────┐
│ <article> 다음 줄로 쌓인다 │
└──────────────────────────┘
이 성질은 display 속성으로 바꿀 수 있어요. 블록 요소에 display: inline을 주면 글자처럼 흐르게 되고, 반대로 인라인 요소에 display: block을 주면 한 줄을 차지하게 돼요.
display: none — 화면에서 통째로 빼기
값이 하나 더 있어요. display: none이에요. 이걸 주면 요소가 화면에서 완전히 사라져요. 단순히 안 보이는 게 아니라, 자리도 차지하지 않아서 마치 처음부터 없던 것처럼 다른 요소들이 그 빈자리를 메워요. 나중에 JavaScript로 "메뉴 열기/닫기"를 만들 때 이 display: none을 켜고 끄는 식으로 자주 써요. 지금은 "요소를 통째로 숨기는 스위치"가 있다는 것만 알아두면 돼요.
🙋 직접 해보세요 — "블록을 인라인으로 바꾸면 어떻게 될까?"
피드 페이지를 열고 개발자 도구(F12) Elements 탭에서 게시물 <article> 하나를 클릭하세요. 오른쪽 Styles 패널 빈 곳에 display: inline;을 직접 입력해보세요. 한 줄을 통째로 쓰던 게시물이 갑자기 글자처럼 쪼그라들면서 옆 게시물과 뒤엉킬 거예요. 블록이 인라인으로 바뀐 거죠. 이번엔 display: none;을 줘보세요. 게시물이 통째로 사라지고 아래 게시물들이 위로 올라와요. 새로고침하면 원래대로 돌아오니 마음껏 실험해보세요.
💡 튜터의 결론: 모든 요소는 한 줄을 통째로 쓰는 block이거나, 글자처럼 옆으로 흐르는 inline이에요. display 속성으로 이 성질을 바꿀 수 있고, display: none은 요소를 자리째 숨겨요. 그런데 block과 inline에는 각각 아쉬운 점이 있어요. 다음 Step에서 둘의 장점만 합친 세 번째 값을 만나봅니다.
Step 2: "display ② — inline-block으로 메뉴를 가로로"
block과 inline은 각각 아쉬운 점이 있어요. block은 가로·세로 크기와 안쪽 여백(padding)을 마음대로 줄 수 있지만, 한 줄을 독차지해서 옆으로 나란히 못 놓아요. 반대로 inline은 옆으로 나란히 흐르지만, 위아래 여백이나 너비 지정을 제대로 못 받아요.
우리 네비게이션 메뉴가 딱 이 문제예요. 메뉴 항목 <li>는 블록이라, 홈·탐색·피드·프로필이 위아래로 주르륵 세로로 쌓여 있어요. 진짜 메뉴 바라면 가로로 나란히 놓여야 하는데 말이죠.
여기서 **display: inline-block**이 등장해요. 이름 그대로 inline과 block의 장점을 합친 값이에요. inline처럼 옆으로 나란히 흐르면서, block처럼 너비·높이·padding을 제대로 받아요. 메뉴를 가로로 눕히기에 딱이죠.
오늘부터 시작하는 새 파일 layout.css에 첫 규칙을 적어봐요.
/* instagram-clone-frontend/css/layout.css */
/* ===== 네비게이션 메뉴 가로 배치 (display: inline-block) ===== */
.nav-menu > li {
display: inline-block;
}
그리고 이 파일을 HTML에 연결해야 화면에 반영돼요. 지난 시간 base.css를 연결했던 것처럼, <head> 안에 한 줄을 더해요.
<!-- instagram-clone-frontend/index.html -->
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/layout.css">
피드 페이지에도 똑같이 연결하고, 메뉴 목록에 nav-menu 클래스가 붙어 있는지 확인해요(지난 시간 로그인 페이지엔 이미 붙여뒀어요).
<!-- instagram-clone-frontend/feed.html -->
<nav>
<ul class="nav-menu">
<li><a href="index.html">홈</a></li>
<li><a href="explore.html">탐색</a></li>
<li><a href="feed.html">피드</a></li>
<li><a href="profile.html">프로필</a></li>
</ul>
</nav>
저장하고 새로고침하면 — 세로로 쌓여 있던 메뉴 항목들이 한 줄에 가로로 나란히 놓여요. 지난 시간 base.css에서 .nav-menu > li에 줬던 회색 배경과 padding도 그대로 살아 있어서, 각 항목이 작은 회색 버튼처럼 옆으로 늘어서죠.
⚠️ 다만 지금은 항목들이 서로 딱 붙어 있고, 메뉴 바 안에서 위아래 정렬이 완벽하진 않아요. 항목 사이 간격을 시원하게 벌리고 양 끝으로 쫙 펴서 정렬하는 건, 가로 배치를 전문으로 다루는 Flexbox에서 제대로 합니다. 지금은 "block을 inline-block으로 바꾸니 가로로 흐른다"는 것까지만 손에 넣으면 충분해요.
💡 튜터의 결론: inline-block은 inline처럼 옆으로 흐르면서 block처럼 크기·여백을 받는 값이에요. 세로로 쌓이던 메뉴를 가로로 눕히는 데 딱이죠. 우리 배치 전담 파일 layout.css의 첫 규칙으로 메뉴 바의 가로 흐름을 만들었어요.
Step 3: "position 입문 — static과 relative"
display가 상자의 "흐름"을 정했다면, 이제 상자의 "위치"를 정할 차례예요. 그 주인공이 position이에요.
기본값부터 봐요. 지금까지 우리가 만든 모든 요소는 position: static 상태예요. static은 "특별한 위치 지정 없이, 문서 흐름이 정해주는 자리에 그냥 놓인다"는 뜻이에요. 블록은 위에서 아래로 쌓이고 인라인은 옆으로 흐르는 — 우리가 지금까지 본 그 자연스러운 흐름이 전부 static이에요. 평소엔 이걸 따로 적지 않아요. 안 적으면 static이거든요.
여기서 한 발 떼는 게 **position: relative**예요. relative를 주면 요소는 일단 원래 흐름의 자리를 그대로 지켜요. 그 상태에서 top·left·right·bottom 값을 주면, 원래 있던 자리를 기준으로 그만큼 살짝 밀려나요. 중요한 건 — 밀려나도 원래 자리는 비워두지 않고 그대로 차지한 것으로 쳐요. 그래서 옆 요소들은 이 요소가 안 움직인 것처럼 행동해요.
[ 원래 흐름 (static) ] [ relative + top:10px left:20px ]
┌─────┐ ┌─────┐ ← 원래 자리는 비어 있는 셈
│ A │ │░░░░░│ (다른 요소는 여기 기준으로 동작)
└─────┘ ↘ ┌─────┐
┌─────┐ │ A │ ← 살짝 밀려난 모습
│ B │ └─────┘
└─────┘ ┌─────┐
│ B │ ← B는 A가 안 움직인 듯 그대로
└─────┘
relative 하나만으론 "살짝 미는" 정도라 그 자체로 화려한 일을 하진 않아요. 하지만 relative에는 숨은 역할이 하나 더 있어요. 자기 안에 있는 다른 요소를 "이 상자 기준으로" 띄울 수 있는 기준점이 되는 거예요. 이게 다음 Step의 핵심 재료라, 지금 relative라는 이름을 꼭 기억해두세요.
🙋 직접 해보세요 — "relative로 밀어도 옆 친구는 그대로일까?"
개발자 도구에서 게시물 <figcaption>(사진 설명)을 클릭하고 Styles 패널에 position: relative;와 left: 30px;를 차례로 입력해보세요. 설명 글이 오른쪽으로 30px 밀려나는데, 그 위아래 다른 요소들은 꿈쩍도 안 해요. 밀려난 자리가 비어 보여도 다른 요소는 "원래 자리에 있다"고 여기기 때문이에요. 이게 relative의 핵심 성질이에요.
💡 튜터의 결론: static은 흐름이 정해주는 기본 위치이고, relative는 원래 자리를 지킨 채 top/left로 살짝 미는 위치예요. relative의 진짜 쓸모는 다음 Step에서 드러나요 — 자기 안의 요소를 띄우는 "기준점" 역할이요.
Step 4: "absolute — 사진 위에 뱃지를 얹기"
이제 진짜 띄우기예요. **position: absolute**를 주면 요소는 원래 흐름에서 완전히 떨어져 나와요. relative와 결정적으로 다른 점은, absolute는 원래 자리를 비워버린다는 거예요. 다른 요소들은 이 요소가 처음부터 없던 것처럼 빈자리를 메워요. 그러고는 둥둥 떠서, top·left·right·bottom이 가리키는 위치로 이동해요.
그런데 "어디를 기준으로" 이동할까요? 여기서 지난 Step의 relative가 돌아와요. absolute 요소는 자기를 감싼 조상 중에 position이 지정된(static이 아닌) 가장 가까운 조상을 기준으로 위치를 잡아요. 만약 그런 조상이 하나도 없으면 화면(뷰포트) 전체를 기준으로 삼고요.
이 성질을 그대로 활용하면 — 사진 위에 뱃지를 얹을 수 있어요. 인스타그램에서 새 게시물이나 광고에 작은 라벨이 사진 모서리에 붙어 있는 그 모습이요. 사진을 감싼 <figure>에 relative를 줘서 기준점으로 만들고, 그 안의 뱃지에 absolute를 줘서 모서리로 띄우면 돼요.
먼저 피드의 첫 게시물 마크업에 뱃지를 한 줄 추가해요.
<!-- instagram-clone-frontend/feed.html -->
<article>
<figure class="post-photo">
<span class="post-badge">NEW</span>
<img src="https://picsum.photos/seed/insta1/600/600"
alt="석양이 지는 제주도 협재 해변 — 하늘이 주황빛으로 물들었다"
width="600" height="600">
<figcaption>오늘의 일상 — 제주도 협재 해변 #sunset #travel</figcaption>
</figure>
그리고 layout.css에 기준점과 뱃지 규칙을 적어요.
/* instagram-clone-frontend/css/layout.css */
/* ===== 게시물 사진 위 뱃지 오버레이 (position: absolute) ===== */
.post-photo {
position: relative;
}
.post-badge {
position: absolute;
top: 0.5rem;
left: 0.5rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
background-color: #0095f6;
color: #ffffff;
font-size: 0.8rem;
font-weight: bold;
}
.post-photo(사진을 감싼 <figure>)에 relative를 줘서 "기준점"으로 삼았어요. 그 안의 .post-badge는 absolute라서 흐름에서 떨어져 나와, 가장 가까운 positioned 조상인 .post-photo의 왼쪽 위 모서리에서 0.5rem씩 떨어진 자리로 떠올라요. 저장하고 보면 파란 NEW 뱃지가 사진 좌상단에 얹혀 있어요.
⚠️ 만약 .post-photo에 relative를 깜빡하면? 뱃지는 기준점을 못 찾고 화면 전체의 왼쪽 위로 날아가버려요. "absolute 자식에는 relative 부모를"이 하나의 세트라고 기억해두세요. 실무에서 정말 자주 쓰는 짝이에요.
🎯 핵심 멘트: absolute는 흐름에서 떨어져 나와 "positioned 조상" 기준으로 떠요. 그 조상을 만드는 게 부모에 주는 position: relative고요. 이 relative + absolute 한 쌍이 "무언가 위에 무언가를 겹쳐 얹는" 모든 UI의 기본 공식이에요.
Step 5: "fixed — 스크롤해도 따라오는 상단 네비게이션 바" ⭐
오늘의 하이라이트예요. **position: fixed**는 요소를 화면(뷰포트) 자체에 고정해요. absolute처럼 흐름에서 떨어져 나오는 건 같은데, 기준이 조상이 아니라 항상 화면이에요. 그래서 페이지를 아무리 스크롤해도 요소는 화면의 그 자리에 딱 붙어 움직이지 않아요.
인스타그램 맨 위 메뉴 바가 바로 이거예요. 피드를 한참 내려도 상단 바는 늘 화면 꼭대기에 있죠. layout.css의 header 규칙으로 만들어봐요.
/* instagram-clone-frontend/css/layout.css */
/* ===== 상단 네비게이션 바 고정 (position: fixed) ===== */
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
padding: 0.5rem 1rem;
background-color: #ffffff;
border-bottom: 1px solid #dbdbdb;
z-index: 100;
}
top: 0과 left: 0으로 화면 왼쪽 위 모서리에 붙이고, width: 100%로 가로를 꽉 채웠어요. fixed는 흐름에서 떨어져 나오느라 너비가 내용만큼만 줄어들기 때문에, width: 100%를 줘야 바가 화면 끝까지 펴져요. 흰 배경과 아래쪽 연회색 테두리로 바와 본문을 구분했고요. 맨 아래 z-index: 100은 "이 바를 다른 것들보다 위에 그려라"는 뜻인데, 자세한 건 다음 Step에서 다뤄요.
fixed의 흔한 함정 — 본문이 가려진다
저장하고 보면 메뉴 바는 멋지게 고정됐는데, 문제가 하나 생겨요. 첫 게시물 윗부분이 메뉴 바에 가려져요. 왜냐면 fixed가 된 헤더는 흐름에서 빠져나가면서 원래 차지하던 자리를 비워버렸고, 그 빈자리로 본문이 위로 쑥 올라왔거든요. 그런데 헤더는 그 본문 위에 둥둥 떠 있으니, 본문 첫머리를 덮어버리는 거예요.
[ 함정: 본문이 헤더에 가린다 ] [ 해결: main에 padding-top ]
┌════════════════════┐ ← fixed ┌════════════════════┐ ← fixed
║ 고정 헤더 (떠 있음) ║ 헤더 ║ 고정 헤더 (떠 있음) ║ 헤더
╠════════════════════╣ ╠════════════════════╣
│ 가려진 게시물 윗부분 │ ← 안 보임 │ (빈 공간) │ ← padding-top
│ 게시물 ... │ ├────────────────────┤
│ 게시물 전체가 보인다 │
해결은 간단해요. 본문 <main>에 헤더 높이만큼 위쪽 안쪽 여백을 줘서, 내용 시작점을 헤더 아래로 밀어내면 돼요.
/* instagram-clone-frontend/css/layout.css */
/* 고정 헤더가 본문 첫 줄을 가리지 않도록 그만큼 아래로 밀기 */
main {
padding-top: 5rem;
}
5rem은 헤더의 대략적인 높이예요. 이만큼 본문을 아래로 내리면, 첫 게시물이 헤더에 안 가리고 온전히 보여요. 이 "fixed 요소를 쓰면 그 높이만큼 본문을 밀어줘야 한다"는 패턴은 고정 헤더를 쓰는 거의 모든 사이트가 똑같이 해요.
🙋 직접 해보세요 — "정말 스크롤해도 따라올까?"
피드 페이지에는 게시물이 여러 개 있어서 스크롤이 생겨요. 마우스 휠로 아래로 쭉 내려보세요. 게시물들은 위로 흘러 지나가는데, 상단 메뉴 바만 화면 꼭대기에 딱 붙어 그대로 있죠? 이게 fixed예요. 이번엔 개발자 도구에서 main의 padding-top: 5rem을 padding-top: 0으로 바꿔보세요. 첫 게시물 윗부분이 헤더 뒤로 숨는 걸 직접 확인할 수 있어요.
💡 튜터의 결론: fixed는 요소를 화면에 고정해서 스크롤과 무관하게 같은 자리에 붙여둬요. 상단 네비게이션 바의 정답이죠. 단, fixed 요소는 흐름에서 빠지므로 본문이 위로 올라와 가려지는데, main에 padding-top을 줘서 그 높이만큼 본문을 밀어 해결해요.
Step 6: "sticky — 흐르다가 화면 위에 딱 붙는 제목"
fixed와 형제처럼 닮았지만 한 끗 다른 값이 있어요. **position: sticky**예요. sticky는 평소엔 static처럼 문서 흐름을 따라 같이 스크롤되다가, 스크롤이 정해둔 임계선에 닿는 순간 그 자리에서 fixed처럼 화면에 딱 붙어요. "흐르다가 멈추는" 하이브리드죠.
피드의 "피드" 섹션 제목에 적용해봐요. 평소엔 게시물과 함께 스크롤되다가, 화면 위쪽 고정 헤더 바로 아래에 닿으면 거기 붙어서 따라오게요.
/* instagram-clone-frontend/css/layout.css */
/* ===== 섹션 제목이 스크롤하다 멈추기 (position: sticky) ===== */
main > h2 {
position: sticky;
top: 4rem;
margin: 0;
padding: 0.5rem 0;
background-color: #fafafa;
}
핵심은 top: 4rem이에요. "화면 위에서 4rem 떨어진 지점에 닿으면 멈춰라"는 뜻이에요. 왜 0이 아니라 4rem이냐면, 위에 이미 fixed 헤더가 5rem쯤 자리를 차지하고 있어서, 제목이 0에 붙으면 헤더 뒤에 숨어버리거든요. 헤더 높이쯤 아래로 띄워 헤더 바로 밑에 멈추게 한 거예요. background-color를 준 건, 제목이 멈춰서 게시물 위에 겹칠 때 글자가 비쳐 보이지 않게 바탕을 깔아준 거고요.
저장하고 스크롤해보면 — "피드" 제목이 게시물과 함께 위로 올라가다가, 헤더 바로 아래에 닿는 순간 멈춰서 따라와요. 그러다 <main> 영역을 다 지나가면 다시 같이 흘러 사라지고요.
⚠️ sticky는 부모 요소의 영역 안에서만 붙어 있어요. 제목이 속한 <main>이 화면을 벗어나면 sticky도 같이 사라져요. "화면에 영원히 고정"인 fixed와 "내 구역 안에서만 따라오는" sticky의 결정적 차이예요.
🎯 핵심 멘트: fixed는 화면에 영원히 고정, sticky는 평소엔 흐르다 임계선(top)에 닿으면 그때부터 부모 구역 안에서만 붙어요. 목차·섹션 헤더·장바구니 요약처럼 "필요할 때만 따라오는" UI에 sticky를 써요.
Step 7: "z-index — 상자가 겹칠 때 누가 위로"
지금까지 absolute·fixed로 요소를 흐름에서 띄웠어요. 그런데 요소들을 띄우다 보면 자연스럽게 생기는 질문이 있어요. 여러 상자가 같은 자리에 겹치면, 누가 위에 보이고 누가 아래에 깔릴까?
기본 규칙은 "나중에 나온 요소가 위"예요. HTML에서 아래쪽에 쓴 요소가 위에 그려져요. 하지만 이걸 내 맘대로 정하고 싶을 때가 있죠. 그때 쓰는 게 **z-index**예요. 화면을 정면에서 본다고 할 때, x축은 가로, y축은 세로라면, z축은 나를 향해 튀어나오는 깊이예요. z-index 값이 클수록 앞으로(위로) 튀어나와 보여요.
화면을 옆에서 본 모습 (z축 = 깊이)
뒤 ←───────────────────────────────→ 앞(나)
게시물 본문 NEW 뱃지 고정 헤더
z-index: auto z-index: 1 z-index: 100
│ │ │
└─ 낮다 ──────────┴──── 높다 ──────┘
우리 layout.css를 다시 보면, 이미 두 군데에 z-index를 써뒀어요. 고정 헤더에는 z-index: 100을, 사진 뱃지에는 z-index: 1을 줬죠. 헤더에 큰 값을 준 건, 스크롤할 때 게시물들이 헤더 자리를 지나가는데 헤더가 그 위에 확실히 보이도록 "맨 앞으로" 끌어올린 거예요. 만약 헤더에 z-index가 없으면, 어떤 떠 있는 요소가 헤더를 덮어버릴 수도 있어요.
여기서 한 가지 주의할 게 있어요. z-index는 position이 지정된 요소에만 먹혀요. static인 평범한 요소에 z-index를 줘봤자 아무 일도 안 일어나요. 그래서 "겹침 순서를 바꾸고 싶으면 먼저 position을 주고, 그 다음 z-index를 준다"가 한 세트예요.
쌓임 맥락(stacking context) — z-index가 안 통할 때
가끔 "분명히 z-index를 999로 줬는데도 다른 요소에 가려진다"는 황당한 일이 생겨요. 범인은 쌓임 맥락(stacking context)이에요. 어떤 요소들은 자기 안에 "독립된 층 묶음"을 만들어요. 그 묶음 안의 z-index 경쟁은 묶음 내부에서만 일어나고, 다른 묶음과는 통째로 순서가 매겨져요.
비유하자면, 아파트 층수 같은 거예요. 5층짜리 A동 꼭대기 집이 아무리 높아도, 10층짜리 B동 1층보다 위로 갈 순 없어요. 동(쌓임 맥락) 자체의 순서가 먼저 정해지고, 층(z-index)은 그 동 안에서만 의미가 있거든요. 지금 단계에선 "z-index가 기대대로 안 먹으면 쌓임 맥락 때문일 수 있다"는 이름만 기억해두면 충분해요. 깊은 규칙은 나중에 더 복잡한 레이아웃을 짤 때 자연스럽게 만나게 돼요.
💡 튜터의 결론: z-index는 겹친 상자들의 앞뒤 순서를 정해요. 값이 클수록 앞(위)으로 와요. 단 position이 지정된 요소에만 먹히고, 가끔 쌓임 맥락 때문에 기대와 다르게 동작할 수 있어요. 우리 고정 헤더가 z-index: 100으로 항상 맨 앞에 오는 이유가 이거예요.
Step 8: "float — 한때 레이아웃의 주역이던 레거시"
마지막으로 옛날이야기를 하나 들려드릴게요. **float**이에요. Flexbox나 Grid 같은 현대적 배치 도구가 나오기 전, 한때 웹의 레이아웃은 거의 다 이 float으로 짰어요. 지금은 거의 안 쓰지만, 옛 코드를 읽거나 그 흔적을 이해하려면 개념은 알아둘 가치가 있어요.
float의 원래 목적은 소박해요. 신문이나 잡지에서 사진 옆으로 글자가 감싸듯 흐르는 모습을 만드는 거였어요. 이미지에 float: left를 주면 이미지가 왼쪽으로 비켜서고, 그 오른쪽 빈 공간으로 텍스트가 흘러 들어가 이미지를 감싸요.
/* (개념 확인용 데모 — 우리 인스타 파일에는 넣지 않아요) */
.magazine-photo {
float: left;
margin-right: 1rem;
}
[ float: left 를 준 이미지 ]
┌─────────┐ 텍스트가 이미지 오른쪽 빈
│ │ 공간으로 흘러 들어가 이미지를
│ 사진 │ 감싸며 이어진다. 이미지 아래
│ │ 까지 내려오면 다시 한 줄을
└─────────┘ 통째로 쓰며 흐른다.
다시 전체 너비로 텍스트가 이어진다...
문제는, 사람들이 이 "옆으로 비켜서는" 성질을 레이아웃 전체를 짜는 데 갖다 썼다는 거예요. 박스 여러 개를 전부 float으로 띄워 가로로 늘어놓는 식이었죠. 그런데 float은 원래 레이아웃용이 아니다 보니, 띄운 요소들이 부모 영역을 삐져나가 부모 높이가 0이 되는 등 골치 아픈 부작용이 많았어요. 그걸 메우려고 clear 같은 땜빵 기법까지 동원해야 했고요.
그래서 가로 배치를 정식으로 다루는 Flexbox(다음 시간 주제)와 Grid가 등장하자, float은 레이아웃 무대에서 빠르게 내려왔어요. 오늘날 float은 원래 목적인 "텍스트가 이미지를 감싸는" 정도에만 가끔 쓰여요.
⚠️ 정리하면 — float으로 레이아웃을 짜지 마세요. 그건 도구가 없던 시절의 임시방편이었어요. "옛 코드에서 float이 보이면 레이아웃을 그렇게 짠 흔적이구나" 정도로 이해하면 충분하고, 우리는 다음 시간부터 제대로 된 도구를 씁니다.
💡 튜터의 결론: float은 원래 "이미지 옆으로 텍스트 감싸기"용이었는데, 한때 레이아웃 전체를 짜는 데 무리하게 쓰였어요. 부작용이 많아 Flexbox·Grid에 자리를 내줬고, 지금은 레거시로 이해만 해두면 돼요.
마무리
오늘 우리는 상자를 "어디에 놓고 어떻게 흐르게 할지" 정하는 도구를 손에 넣었어요. 박스 모델로 상자의 크기와 여백을 다뤘다면, 오늘은 그 상자를 화면 위에 배치했죠. 그 결과 휑하던 페이지에 진짜 인스타그램처럼 스크롤해도 따라오는 상단 메뉴 바가 생겼고요.
오늘 배운 것 한눈에 정리
배치는 크게 두 축이었어요. 흐름을 정하는 display, 그리고 **위치를 정하는 position**이요.
🎯 하나, display — block은 한 줄을 통째로 쓰고, inline은 글자처럼 흐르고, inline-block은 둘의 장점을 합쳐요. display: none은 요소를 자리째 숨겨요. 메뉴를 가로로 눕힌 게 inline-block이었죠.
🎯 둘, position의 다섯 값 — static(기본 흐름), relative(원래 자리 지키며 살짝 밀기 + 기준점 역할), absolute(흐름에서 빠져 positioned 조상 기준으로 띄우기), fixed(화면에 고정), sticky(흐르다 임계선에서 멈춤).
🎯 셋, 인스타 연결 — relative + absolute로 사진 위에 NEW 뱃지를 얹고, fixed로 상단 네비 바를 고정했어요. fixed의 본문 가림 함정은 main의 padding-top으로 풀었고요.
🎯 넷, z-index — 겹친 상자의 앞뒤 순서. 값이 클수록 앞으로. position이 있어야 먹히고, 가끔 쌓임 맥락이 끼어들어요.
🎯 다섯, float — 옛 레이아웃 도구. 지금은 레거시로 이해만.
그런데 가로 정렬은 아직 거칠죠
오늘 inline-block으로 메뉴를 가로로 눕히긴 했지만, 항목 사이 간격이나 위아래 정렬은 아직 거칠어요. 로그인 폼도 margin auto로 가로 가운데까지만 왔지, 화면 정중앙에 띄우진 못했고요. 게시물의 좋아요·댓글 아이콘을 한 줄에 가지런히 정렬하는 일도 아직 못 했어요.
이런 "여러 요소를 한 줄에 놓고, 간격을 고르게 벌리고, 가운데로 모으는" 일을 전문으로 다루는 도구가 따로 있어요. 바로 Flexbox예요.
다음 시간 예고
다음 시간엔 가로·세로 정렬의 끝판왕 Flexbox를 배웁니다. 부모 상자에 display: flex 한 줄만 주면, 그 안의 자식들을 한 줄에 나란히 놓고, 간격을 고르게 벌리고(justify-content), 위아래로 가운데 맞추고(align-items), 남는 공간을 비율로 나눠 갖는(flex-grow) 일을 전부 할 수 있어요. 오늘 margin auto로 가로만 맞췄던 로그인 폼을 화면 정중앙에 띄우고, 게시물도 깔끔한 카드 레이아웃으로 정리할 거예요.
오늘 상자를 "어디에 놓을지" 큰 그림을 잡았으니, 다음 시간엔 그 상자들을 줄 맞춰 정렬하는 섬세한 솜씨를 익혀봅시다!
과제
[구현] 화면 하단에 고정되는 모바일 탭 바 만들기
오늘 position: fixed로 메뉴 바를 화면 위에 붙였죠. 이번엔 반대로, 푸터(<footer>)를 화면 아래에 딱 붙여 모바일 인스타그램의 하단 탭 바처럼 만들어봐요. layout.css에 직접 규칙을 추가하세요.
요구 사항:
footer에position: fixed를 주고,top: 0대신 **bottom: 0**으로 화면 바닥에 붙이기.left: 0과width: 100%로 가로를 꽉 채우고,background-color(흰색)와 위쪽 테두리(border-top)로 본문과 구분.- 헤더 때와 똑같은 함정이 생겨요 — 고정된 푸터가 마지막 게시물 아랫부분을 가려요.
main에padding-bottom을 줘서 마지막 게시물이 푸터에 안 가리도록 해결하기. - 푸터가 다른 요소에 안 가리고 위에 보이도록
z-index도 적절히 주기.
저장하고 스크롤해보면서, 푸터가 바닥에 고정되는지 / 마지막 게시물이 안 가려지는지 둘 다 확인하세요. top 대신 bottom을 쓴다는 것 외엔 헤더와 똑같은 패턴이에요.
[구현·응용] 게시물에 좋아요 수 뱃지 얹기
Step 4에서 사진 왼쪽 위에 NEW 뱃지를 얹었어요. 이번엔 같은 absolute 기법으로, 사진 오른쪽 아래에 좋아요 수를 보여주는 작은 뱃지(예: ♥ 1,240)를 얹어봐요.
요구 사항:
- 피드의 두 번째 게시물
<figure>에도post-photo클래스를 주고(기준점 만들기), 그 안에 좋아요 수를 담은 요소를 추가. - 새 뱃지 규칙은
absolute로 띄우되,top/left대신 **bottom/right**를 써서 오른쪽 아래 모서리에 배치. - 배경을 반투명 검정(예:
rgba(0, 0, 0, 0.5))으로, 글자는 흰색으로 줘서 사진 위에서도 잘 보이게.
top·left로 왼쪽 위에 붙이듯, bottom·right로 오른쪽 아래에 붙일 수 있다는 걸 직접 확인하세요. 핵심은 "absolute 자식에는 relative 부모"라는 한 쌍을 또 한 번 손에 익히는 거예요.
[탐구] 좋아하는 사이트의 "따라오는 요소" 분석하기
평소 자주 쓰는 웹사이트 두 곳을 열고, 스크롤할 때 따라오는 요소를 찾아보세요. 그리고 개발자 도구(F12) Elements 탭에서 그 요소를 클릭해 Computed 또는 Styles 패널에서 position 값을 확인하세요.
확인 항목:
- 그 따라오는 요소의
position은fixed인가,sticky인가? fixed라면 화면 어디에 붙어 있나(위/아래/옆)?top·bottom값은 얼마인가?z-index는 얼마로 주고 있나? (다른 요소 위에 확실히 뜨게 큰 값을 주는 편인가)- 그 사이트는 고정 요소 높이만큼 본문에 여백(
padding)을 줬나? 안 줬다면 본문이 가려지나?
관찰한 내용을 3~4줄로 정리하고, "이 요소는 왜 fixed(또는 sticky)를 골랐을까" 한 줄 의견을 덧붙여주세요.
생각해볼 주제
1. fixed와 sticky는 둘 다 "따라오는데", 언제 무엇을 골라야 할까?
상단 메뉴 바는 fixed로, 섹션 제목은 sticky로 만들었어요. 둘 다 스크롤할 때 화면에 붙어 따라온다는 점은 비슷한데, 결정적 차이가 있죠. fixed는 페이지 어디에 있든 늘 같은 자리에 떠 있고, sticky는 자기 부모 구역을 벗어나면 같이 사라져요. 만약 인스타그램 상단 메뉴 바를 sticky로 만들었다면 어떤 어색한 일이 생길까요? 반대로, 긴 글의 "목차"나 "이 챕터 제목"을 fixed로 만들면 왜 곤란할까요? "화면에 영원히"와 "내 구역에서만"이라는 두 성질이 각각 어떤 UI에 어울리는지 생각해보세요.
2. 모든 요소를 absolute로 좌표 찍어 배치하면 안 될까?
absolute에 top·left로 정확한 위치를 주면, 마치 그림판처럼 요소를 화면 원하는 좌표에 콕콕 박을 수 있어요. 그러면 "전부 absolute로 좌표만 찍으면 레이아웃이 끝나는 것 아닌가?" 싶을 수 있죠. 하지만 실무에서는 이렇게 안 해요. 화면 크기가 제각각인 휴대폰·태블릿·데스크톱에서 같은 페이지를 본다고 상상해보세요. 모든 요소가 고정 좌표로 박혀 있으면 무슨 일이 생길까요? 또 게시물이 100개로 늘어나면? "흐름(normal flow)에 맡기는 배치"와 "좌표로 박는 배치"가 각각 어떤 상황에 강하고 약한지 생각해보세요.
3. z-index를 9999로 도배하면 왜 나중에 곤란해질까?
요소가 다른 것에 가려질 때, 가장 빠른 해결은 z-index를 큰 값으로 주는 거예요. 그래서 막상 급하면 z-index: 9999를 던지게 되죠. 그런데 팀 동료들도 다 그렇게 하면, 곧 99999, 999999가 등장하는 "숫자 키우기 경쟁"이 벌어져요. 게다가 분명히 9999를 줬는데도 안 먹히는 경우가 생기고요(쌓임 맥락을 떠올려보세요). 왜 z-index를 무작정 키우는 게 근본 해결이 아닐까요? 여러 사람이 함께 짜는 프로젝트에서 겹침 순서를 어떻게 관리하면 좋을지, "숫자를 키운다"가 아닌 다른 접근을 상상해보세요.
✅ 예시 답안정답 보기
과제와 생각해볼 주제의 예시답안이에요. 정답이 하나만 있는 건 아니에요. 여백 값이나 색은 취향대로 골라도 좋아요. 중요한 건
display로 흐름을,position으로 위치를 의도대로 다뤘는가 예요.
🎯 [과제 1 예시답안] 화면 하단에 고정되는 모바일 탭 바 만들기
핵심 접근
이 과제는 Step 5의 고정 헤더를 위아래만 뒤집은 거예요. 헤더는 top: 0으로 화면 위에 붙였으니, 푸터는 bottom: 0으로 화면 아래에 붙이면 돼요. 그리고 헤더에서 겪은 "본문이 가려지는 함정"이 여기서도 똑같이 일어나요. 헤더는 padding-top으로 풀었으니, 푸터는 padding-bottom으로 풀면 되고요. "위 ↔ 아래"만 바꾼 같은 패턴이라는 걸 알아채는 게 이 과제의 핵심이에요.
예시 구현
layout.css에 푸터 고정 규칙을 추가해요.
/* instagram-clone-frontend/css/layout.css 에 추가 */
/* ===== 하단 탭 바 고정 (position: fixed, bottom) ===== */
footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding: 0.75rem 1rem;
background-color: #ffffff;
border-top: 1px solid #dbdbdb;
text-align: center;
z-index: 100;
}
그리고 마지막 게시물이 푸터에 가리지 않도록, main의 아래쪽 여백을 더해요. Step 5에서 padding-top: 5rem을 줬으니, 한 줄로 위아래를 함께 적어요.
/* main 규칙 — 위(헤더)·아래(푸터) 둘 다 여백 */
main {
padding-top: 5rem;
padding-bottom: 4rem;
}
bottom: 0으로 푸터를 화면 바닥에 붙이고, width: 100%로 가로를 채웠어요. 헤더는 아래쪽 테두리(border-bottom)로 본문과 나눴다면, 푸터는 위쪽 테두리(border-top)로 나눠요. z-index: 100은 헤더와 같은 값으로 줘서, 둘 다 본문 위에 확실히 뜨게 했어요.
저장하고 스크롤해보면 — 푸터가 화면 바닥에 딱 붙어 따라오고, 마지막 게시물도 padding-bottom 덕분에 푸터에 안 가려요.
채점 포인트
| 항목 | 확인 내용 |
|---|---|
| fixed + bottom | position: fixed와 bottom: 0으로 화면 아래에 붙였는가 |
| 가로 채우기 | left: 0 + width: 100%로 바를 화면 끝까지 폈는가 |
| 본문 가림 해결 | main에 padding-bottom을 줘서 마지막 게시물이 안 가리게 했는가 |
| z-index | 푸터가 본문 위에 뜨도록 z-index를 줬는가 |
흔한 실수
width: 100%를 빠뜨림 —fixed는 흐름에서 빠지면서 너비가 내용만큼 줄어들어요.width: 100%가 없으면 푸터가 글자 너비만큼만 좁게 나와요.padding-bottom을 안 줌 — 평소엔 멀쩡해 보이다가, 맨 아래까지 스크롤하면 마지막 게시물 끝이 푸터에 가려요. 고정 요소 높이만큼 본문에 여백을 주는 걸 잊지 마세요.bottom과top을 동시에 줌 — 둘 다 주면 의도와 다르게 위아래로 늘어날 수 있어요. 바닥 고정엔bottom만.
실무 개선 포인트 (심화)
진짜 모바일 앱의 하단 탭 바는 아이폰 같은 기기의 둥근 모서리·홈 인디케이터 영역을 피해야 해요. 그래서 실무에선 padding-bottom: env(safe-area-inset-bottom) 같은 값으로 "안전 영역"을 확보해요. 지금은 몰라도 되지만, "고정 바는 기기 가장자리를 신경 써야 한다"는 감만 알아두면 좋아요.
🎯 [과제 2 예시답안] 게시물에 좋아요 수 뱃지 얹기
핵심 접근
Step 4의 NEW 뱃지와 완전히 같은 공식이에요. "absolute 자식에는 relative 부모." 사진을 감싼 <figure>에 relative를 줘서 기준점을 만들고, 그 안의 좋아요 뱃지에 absolute를 주면 돼요. 딱 하나만 바꿔요 — NEW 뱃지는 top·left로 왼쪽 위에 붙였으니, 좋아요 뱃지는 bottom·right로 오른쪽 아래에 붙이는 거예요.
예시 구현
먼저 두 번째 게시물 <figure>에 post-photo 클래스와 좋아요 뱃지를 추가해요.
<!-- instagram-clone-frontend/feed.html — 두 번째 게시물 -->
<article>
<figure class="post-photo">
<span class="post-likes">♥ 1,240</span>
<img src="https://picsum.photos/seed/insta2/600/600"
alt="동네 카페 테이블 위에 놓인 라떼아트가 그려진 커피잔"
width="600" height="600"
loading="lazy">
<figcaption>오늘 발견한 동네 카페 #coffee #latte</figcaption>
</figure>
</article>
post-photo 클래스는 Step 4에서 만든 그대로라 position: relative가 이미 먹혀요. 이제 좋아요 뱃지 규칙만 layout.css에 추가해요.
/* instagram-clone-frontend/css/layout.css 에 추가 */
/* ===== 사진 오른쪽 아래 좋아요 뱃지 (absolute, bottom·right) ===== */
.post-likes {
position: absolute;
bottom: 0.5rem;
right: 0.5rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.5);
color: #ffffff;
font-size: 0.8rem;
font-weight: bold;
}
bottom: 0.5rem과 right: 0.5rem으로 사진 오른쪽 아래 모서리에서 0.5rem씩 떨어진 자리에 띄웠어요. 배경을 반투명 검정(rgba(0, 0, 0, 0.5))으로 준 게 포인트예요 — 사진이 밝든 어둡든 흰 글자가 항상 잘 보이거든요.
채점 포인트
| 항목 | 확인 내용 |
|---|---|
| relative 부모 | 두 번째 <figure>에 post-photo(relative)를 줘서 기준점을 만들었는가 |
| absolute 자식 | 좋아요 뱃지에 position: absolute를 줬는가 |
| bottom·right | top/left가 아닌 bottom/right로 오른쪽 아래에 배치했는가 |
| 가독성 | 반투명 배경 등으로 사진 위에서 글자가 잘 보이게 했는가 |
흔한 실수
post-photo(relative)를 깜빡 — 좋아요 뱃지가 기준점을 못 찾고 화면 전체 오른쪽 아래로 날아가요. Step 4의 함정 그대로예요.bottom과top을 헷갈림 —top: 0.5rem을 주면 오른쪽 위에 붙어요. 아래 모서리는bottom이에요.- 배경 없이 흰 글자만 — 밝은 사진 위에선 흰 글자가 안 보여요. 반투명 배경이나 그림자로 대비를 줘야 해요.
실무 개선 포인트 (심화)
사진 위에 글자를 얹을 땐, 사진 색에 따라 글자가 묻히는 게 늘 골칫거리예요. 실무에선 사진 아래쪽에 어두운 그라데이션(linear-gradient)을 깔아 글자 영역만 살짝 어둡게 만드는 기법을 자주 써요. 인스타그램·유튜브 썸네일의 제목 글자가 잘 보이는 이유가 이거예요. 지금은 반투명 배경 한 겹으로 충분하지만, "사진 위 글자엔 대비 장치가 필요하다"는 원칙만 기억해두세요.
🎯 [과제 3 예시답안] 좋아하는 사이트의 "따라오는 요소" 분석하기
핵심 접근
이 과제는 코드를 짜는 게 아니라, 오늘 배운 fixed·sticky·z-index가 실제 사이트에서 어떻게 쓰이는지 눈으로 확인하는 거예요. 개발자 도구 Elements 탭에서 요소를 클릭하고 Computed 패널에서 position을 검색하면 바로 보여요.
분석 가이드
- 스크롤할 때 따라오는 요소를 클릭 →
Styles또는Computed패널에서position값 확인 fixed인지sticky인지 구분 — 페이지 끝까지 따라오면fixed, 어느 구간에서만 따라오면sticky일 가능성이 커요top·bottom값으로 어디에 붙는지,z-index로 얼마나 앞에 두는지 확인- 고정 요소 높이만큼 본문에 여백을 줬는지 확인
모범 정리 예시
어떤 쇼핑몰 사이트의 상단 검색 바를 분석했어요.
position: sticky에top: 0을 쓰고 있었어요. 처음엔 배너 아래에 있다가, 스크롤해서 화면 위에 닿으면 거기 붙어 따라왔어요.z-index는50이었고요. 상품 목록 위로 확실히 뜨게 한 거죠.fixed가 아니라sticky를 쓴 건, 페이지 맨 위에선 배너와 함께 자연스럽게 흐르다가 필요할 때만 붙게 하려는 의도 같아요.
채점 포인트
| 항목 | 확인 내용 |
|---|---|
| position 확인 | 따라오는 요소의 position 값을 실제로 확인했는가 |
| fixed/sticky 구분 | 둘 중 무엇인지 근거를 들어 구분했는가 |
| z-index 관찰 | z-index 값을 확인하고 그 의미를 해석했는가 |
| 의견 | "왜 이 값을 골랐을까" 나름의 해석을 덧붙였는가 |
흔한 실수
position을Styles에서 못 찾음 — 직접 지정 안 한 속성은Styles에 안 보일 수 있어요. 그럴 땐Computed탭에서position을 검색하면 최종 적용값이 나와요.relative를 보고 "따라온다"고 오해 —relative만으론 안 따라와요. 따라오는 건fixed나sticky예요.
실무 개선 포인트 (심화)
요즘 사이트는 "위로 스크롤하면 헤더가 나타나고, 아래로 내리면 숨는" 똑똑한 헤더를 많이 써요. 이건 fixed/sticky에 JavaScript로 스크롤 방향을 감지해 클래스를 토글하는 식으로 만들어요. 지금은 CSS만으로 고정하는 단계지만, 나중에 JavaScript를 배우면 이런 동적인 헤더도 만들 수 있어요.
💡 [생각해볼 주제 1 예시답안] fixed와 sticky는 둘 다 "따라오는데", 언제 무엇을 골라야 할까?
[문제 상황 요약]
fixed와 sticky는 둘 다 스크롤할 때 화면에 붙어 따라와요. 비슷해 보이지만, fixed는 페이지 어디서든 늘 같은 자리에 떠 있고, sticky는 자기 부모 구역을 벗어나면 같이 사라져요. 이 차이가 UI 선택에 어떤 영향을 주는지 생각해보는 문제예요.
[튜터의 가이드 및 해설]
핵심은 "항상 보여야 하는가, 필요할 때만 보이면 되는가"예요.
상단 메뉴 바·하단 탭 바처럼 언제나 손이 닿아야 하는 요소는 fixed가 맞아요. 사용자가 페이지 어디에 있든 "홈으로", "검색", "프로필"은 늘 그 자리에 있어야 하니까요. 만약 메뉴 바를 sticky로 만들면, 페이지 맨 아래 푸터 근처에선 메뉴가 사라져버려요. 가장 필요한 순간에 메뉴가 없는 셈이죠.
반대로 긴 글의 "현재 챕터 제목"이나 표의 "열 머리글"처럼 그 구역 안에서만 의미 있는 요소는 sticky가 맞아요. 챕터 제목은 그 챕터를 읽는 동안만 위에 붙어 있으면 되고, 다음 챕터로 넘어가면 새 제목이 밀고 올라와야 자연스러워요. 이걸 fixed로 만들면 모든 챕터를 지나도 첫 제목이 화면에 영원히 박혀 있어서, 지금 읽는 내용과 안 맞는 엉뚱한 제목이 떠 있게 돼요.
정리하면 — fixed는 "페이지 전역에서 항상", sticky는 "특정 구역 안에서만"이에요. 따라오는 게 비슷하다고 아무거나 쓰면, 가장 중요한 순간에 사라지거나(메뉴를 sticky로) 엉뚱하게 남아 있는(챕터 제목을 fixed로) 어색함이 생겨요.
🎯 면접관을 홀리는 핵심 멘트
"
fixed는 뷰포트 기준 전역 고정,sticky는 부모 구역 안에서 임계선에 닿을 때만 고정돼요. 그래서 전역 네비게이션은fixed, 섹션 헤더나 목차처럼 문맥에 묶인 요소는sticky로 갑니다. '항상 필요한가, 그 구간에서만 필요한가'가 선택 기준이에요."
💡 [생각해볼 주제 2 예시답안] 모든 요소를 absolute로 좌표 찍어 배치하면 안 될까?
[문제 상황 요약]
absolute에 top·left를 주면 요소를 화면 원하는 좌표에 정확히 박을 수 있어요. "그러면 전부 absolute로 좌표만 찍으면 레이아웃 끝 아닌가?" 싶지만, 실무에선 이렇게 안 해요. 왜 흐름(normal flow)에 맡기는 배치가 기본이고, absolute는 예외적으로만 쓰는지 생각해보는 문제예요.
[튜터의 가이드 및 해설]
absolute로 좌표를 박는 배치의 가장 큰 약점은 화면 크기가 바뀌면 무너진다는 거예요.
화면을 떠올려보세요. 휴대폰은 좁고, 데스크톱은 넓어요. 흐름에 맡긴 요소는 화면 너비에 따라 자연스럽게 줄바꿈되고 늘어나고 줄어들어요. 그런데 top: 200px; left: 350px처럼 좌표를 박아두면, 그 위치는 화면 크기와 상관없이 고정이에요. 좁은 휴대폰에선 요소가 화면 밖으로 삐져나가거나 서로 겹쳐버려요. 반응형이 불가능해지는 거죠.
두 번째 약점은 내용이 늘어나면 깨진다는 거예요. 게시물이 10개에서 100개로 늘어난다고 해봐요. 흐름에 맡긴 게시물은 그냥 아래로 차곡차곡 쌓이면 끝이에요. 하지만 좌표를 박았다면? 게시물 100개의 좌표를 일일이 계산해서 박아야 하고, 하나가 길어지면 그 아래 전부를 다시 계산해야 해요. 흐름은 이걸 브라우저가 알아서 해주는데, absolute는 그 자동 계산을 통째로 포기하는 거예요.
그래서 원칙은 — 배치의 99%는 흐름(과 곧 배울 Flexbox·Grid)에 맡기고, absolute는 "흐름 위에 뭔가를 겹쳐 얹을 때"만 쓴다예요. 오늘 사진 위 NEW 뱃지가 딱 그 예외였죠. 뱃지는 사진 흐름과 무관하게 모서리에 떠 있어야 하니까요. absolute는 강력하지만, 흐름의 자동 계산을 버리는 대가가 있다는 걸 알고 골라 써야 해요.
🎯 면접관을 홀리는 핵심 멘트
"
absolute는 요소를 흐름에서 떼어내 좌표로 박기 때문에, 반응형 대응과 콘텐츠 증가에 취약해요. 그래서 레이아웃의 뼈대는 normal flow와 Flexbox·Grid에 맡기고,absolute는 뱃지·툴팁·오버레이처럼 '흐름 위에 겹쳐 얹는' 국소적 용도로 한정합니다."
💡 [생각해볼 주제 3 예시답안] z-index를 9999로 도배하면 왜 나중에 곤란해질까?
[문제 상황 요약]
요소가 가려질 때 z-index를 큰 값으로 주면 바로 해결돼요. 그래서 급하면 z-index: 9999를 던지게 되죠. 하지만 팀원 모두가 그러면 99999, 999999로 숫자만 커지는 경쟁이 벌어지고, 가끔은 큰 값을 줘도 안 먹혀요. 왜 이게 근본 해결이 아닌지 생각해보는 문제예요.
[튜터의 가이드 및 해설]
먼저 z-index를 키워도 안 먹히는 경우부터 짚을게요. 범인은 오늘 배운 쌓임 맥락(stacking context)이에요. 어떤 요소가 독립된 "층 묶음"을 만들면, 그 안의 자식은 아무리 z-index를 키워도 묶음 바깥의 요소를 못 넘어요. 아파트 비유로, 5층짜리 동의 꼭대기 집이 10층짜리 옆 동 1층보다 위로 못 가는 것처럼요. 그래서 9999를 줬는데도 가려진다면, 숫자가 작아서가 아니라 묶음 자체의 순서 때문이에요. 숫자를 더 키우는 건 헛수고죠.
다음으로 숫자 경쟁의 문제예요. 한 사람이 9999를 쓰면, 그 위에 뭔가를 올려야 하는 다음 사람은 10000을 써요. 그 다음은 99999. 이렇게 의미 없이 커진 숫자들은, 나중에 "이 요소는 대체 몇을 줘야 위로 오지?"를 아무도 예측할 수 없게 만들어요. 코드를 읽어도 왜 이 값인지 알 수 없고요.
근본 해결은 z-index에 질서를 정하는 것이에요. 실무에선 "헤더는 100, 드롭다운은 200, 모달은 300, 토스트 알림은 400" 식으로 층위를 미리 약속해둬요. 그러면 누구나 "내 요소는 모달보다 위, 토스트보단 아래니까 350쯤"으로 예측 가능하게 정할 수 있어요. 오늘 우리가 헤더에 100, 뱃지에 1을 준 것도 이런 질서의 출발이에요. 숫자를 키우는 게 아니라, 숫자에 의미를 부여하는 거죠.
🎯 면접관을 홀리는 핵심 멘트
"
z-index가 안 먹히면 값이 작아서가 아니라 쌓임 맥락 때문인 경우가 많아요. 그래서9999같은 임의의 큰 값 대신, 헤더·드롭다운·모달·토스트처럼 UI 층위별로 z-index 범위를 미리 약속해 관리합니다. 숫자를 키우는 게 아니라 숫자에 규칙을 주는 거예요."