LabHub
배우기 러닝패스 코스

CSS — 나중에 쓴 규칙이 이긴다 · sticky 라고 썼는데 안 붙는다 · 이론

원인은 언제나 조상에 있다

LabHub 에서 이어서 보기

한 줄 요약

fixed 가 화면에 안 붙고 sticky 가 안 따라다닐 때, 원인은 그 요소가 아니라 조상에 있다. 담음 블록과 스크롤 상자를 누가 만들었는지 찾으면 답이 나온다.

왜 이게 필요했나

이 두 버그는 증상이 원인을 전혀 가리키지 않는다.

둘 다 그 요소의 CSS 는 완벽하게 맞다. 개발자 도구로 그 요소만 보면 아무 문제가 없다. 그래서 사람들은 값을 바꿔 가며 몇 시간을 쓴다. 볼 곳은 조상이다.

어떻게 동작하나 — 담음 블록

top, left, width: 50% 같은 값은 전부 담음 블록(containing block) 을 기준으로 잰다. 그 담음 블록이 무엇인지는 position 값이 정한다. [MDN 의 담음 블록 문서](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_display/Containing_block)가 규칙을 정리한다.

| position | 담음 블록 |
|---|---|
| static · relative | 가장 가까운 블록 조상의 content 영역 |
| absolute | 가장 가까운 위치 지정된(static 이 아닌) 조상의 padding 영역 |
| fixed | 뷰포트 |
| sticky | 흐름 기준은 부모, 붙는 기준은 가장 가까운 스크롤 상자 |

여기까지는 대부분 안다. 문제는 그다음이다.

fixed 를 가두는 속성들

조상 중 어느 하나라도 아래 속성을 가지면, 그 조상이 fixed 와 absolute 의 새 담음 블록이 된다.

transform          (none 이 아닌 값)filterbackdrop-filterperspectivewill-change: transform | filter | perspectivecontain: layout | paint | strict | content

그래서 이런 일이 난다. 카드에 마우스를 올리면 살짝 떠오르는 효과를 주려고 transform: translateY(-2px) 을 넣는다. 또는 성능을 위해 will-change: transform 을 넣는다. 그 순간 카드 안에 있던 모달 덮개가 카드 안에 갇힌다. 애니메이션과 모달은 아무 관계가 없어 보이므로 원인을 찾기까지 오래 걸린다.

[CSS Positioned Layout 3](https://www.w3.org/TR/css-position-3/)과 [CSS Containment 3](https://www.w3.org/TR/css-contain-3/)이 각각의 규정을 담고 있다.

sticky 가 안 붙는 두 가지 이유

position: sticky 는 두 조건이 모두 맞아야 동작한다.

첫째, 임계값이 있어야 한다. top, right, bottom, left 중 하나 이상이 auto 가 아니어야 한다. 하나도 없으면 붙을 선이 없으므로 그냥 흘러간다. 오류도 경고도 없다.

둘째, 스크롤 상자가 실제로 스크롤되어야 한다. sticky 는 가장 가까운 스크롤 상자 안에서만 붙는다. 스크롤 상자는 overflowvisible 이 아닌 가장 가까운 조상이다.

여기서 overflow: hidden 이 사고를 낸다. 가로 스크롤을 막으려고 중간 컨테이너에 overflow-x: hidden 을 넣으면, overflow-y 도 함께 auto 가 되어 그 상자가 스크롤 상자가 된다. 사용자는 그 상자를 스크롤하지 않으므로(페이지를 스크롤한다) sticky 가 붙을 일이 영영 생기지 않는다.

그리고 sticky 의 붙는 범위는 부모 상자 안이다. 부모가 자기 높이만큼만 있으면 그만큼만 따라온다. "조금 따라오다 만다" 는 증상은 거의 항상 이것이다.

z-index 는 어느 상자 안에서 겨루는가

z-index: 9999 를 줬는데도 뒤에 깔리는 일이 있다. 숫자가 작아서가 아니라 다른 쌓임 맥락 안에 있어서다.

쌓임 맥락은 이런 것들이 만든다. [MDN 의 쌓임 맥락 문서](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context)가 전체 목록을 준다.

조상이 쌓임 맥락을 만들면 그 안의 z-index그 맥락 안에서만 통한다. 부모가 z-index: 1 이고 형제가 z-index: 2 면, 부모 안의 자식이 9999 여도 그 형제 아래다. opacity: 0.99 같은 무심한 한 줄이 이것을 만든다.

현장에서 만나는 모습

세 가지가 반복해서 나온다.

첫째, 드롭다운이 부모에 잘린다. 조상에 overflow: hidden 이 있고 드롭다운이 absolute 다. fixed 로 바꾸면 되는데, 그 조상에 transform 이 있으면 fixed 도 갇힌다. 근본 해결은 메뉴를 문서 최상단으로 옮기는 것(포털)이다.

둘째, 모바일에서만 헤더가 안 붙는다. 좁은 화면에서 가로 넘침을 막으려고 overflow-x: hidden 을 넣은 미디어 쿼리가 있었던 것이다.

셋째, iOS 에서만 이상하다. 이건 대개 -webkit-overflow-scrolling 이나 안전 영역(env(safe-area-inset-*)) 문제인데, 여기서 다루는 규칙과는 다른 축이라 실제 기기에서 확인해야 한다. 추측으로 고치지 않는 편이 낫다.

진단 순서를 외워 두면 대부분 5분 안에 끝난다.

> 1. 이 요소의 position 이 무엇인가
> 2. 담음 블록이 무엇인가 — 조상을 따라 올라가며 transform 계열을 찾는다
> 3. sticky 라면 임계값이 있는가, 스크롤 상자가 어느 것인가
> 4. z-index 라면 조상 중 누가 쌓임 맥락을 만들었는가

다음 실습에서 할 것

정해진 문서 구조에 스타일시트를 쓰고, 담음 블록과 스크롤 상자를 계산기로 직접 찾는다. 카드에 transform 을 넣으면 덮개가 어떻게 갇히는지, 조상의 overflow: hidden 이 sticky 를 어떻게 죽이는지를 눈으로 확인한다.