네가 왜 거기서 나와
예전에 z-index가 적용안되는 문제를 겪고 처음 쌓임맥락 글을 읽었을 때 유난히 이해가 안갔다.
🔽 MDN의 쌓임 맥락 설명. 잘 모를 때 보면 순간 아득해진다.
프로젝트에서 사이드바를 구현하면서 쌓임 맥락을 만지다보니 이해가 되어서 포스팅에도 예시를 통해 설명해보려 한다.
스크롤하면 목차가 위치에 맞춰 옆으로 튀어나오는 사이드바를 구현하는 게 목표다.
사이드바는 뷰포트에 고정되는 요소이니 position: fixed
로 먼저 구현해보자. 최 상단 목차 요소가 들어갈 수 있을 정도의 width를 주고 스크롤이 되도록 설정한다.
<div id="app" class="app">
<aside class="sidebar">
<a class="sidebar__item">목차 제목</a>
<!-- 나머지 요소 여러개 -->
<div class="sidebar__scroll-helper"></div>
</aside>
<main id="main" class="main">
<!-- 생략 -->
</main>
</div>
.sidebar {
position: fixed;
width: 18rem;
height: 100vh;
overflow-y: scroll;
}
.sidebar__item {
display: block;
/** transform 관련 속성 생략 **/
}
.sidebar__scroll-helper {
height: calc(100% - 2rem);
}
위처럼 설정하면 화면 옆에 고정된 채로 스크롤도 정상적으로 작동한다.
문제는 사이드바 너비만큼 main
의 이벤트가 막힌다.
이벤트가 막히는 이유는 position이 fixed
로 설정되어 새로운 쌓임 맥락이 생성되었고 main
상단의 레이어에 sidebar
가 렌더링되었기 때문이다.
사이드바 배경이 투명해서 main
의 콘텐츠는 보인다. 그런데 스크롤을 비롯한 이벤트가 막힌다면 사용자 경험이 저해된다. 사이드 바 배경을 main
뒤로 보내면 해결될 것 같으니 z-index
로 시도해보자.
/** 기존 속성 생략 **/
.sidebar {
z-index: 0;
}
.main {
z-index: 1;
}
.sidebar-item {
z-index: 2;
}
분명 순서에 맞게 설정했지만 의도대로 적용되지 않고 그대로 렌더링된다.
서로 다른 쌓임 맥락에 포함된 요소 간에는 z-index
를 이용한 렌더링 레이어 순서 변경이 불가능하다. z-index
는 하나의 쌓임 맥락 내에서 적용되는 속성이기 때문이다.
정확히 말하자면
z-index
는 새로운 쌓임 맥락을 만드는데, 쌓임 맥락은 부모의 쌓임 맥락을 기반으로 존재하기에 하나의 쌓임 맥락 내에서 높낮이가 적용되는 것처럼 보인다.
사이드바의 배경만 main
뒤로 보내려면 일단 sidebar
와 main
이 하나의 쌓임 맥락 안에서 같이 존재해야한다. 일단 fixed
를 제거해본다.
.sidebar {
/** position: fixed; 제거 **/
/** 생략 **/
}
사이드바가 flex box 내부 요소가 되어버려서 main
과 공간을 나누게 된다. width
와 overflow-x
로 차지하는 공간을 없에되 뷰포트 안에는 위치하도록 설정한다.
<aside class="sidebar">
<div class="sidebar__inner">
<a class="sidebar__item">목차 제목</a>
<!-- 나머지 요소 여러개 -->
<div class="sidebar__scroll-helper"></div>
</div>
</aside>
.sidebar {
width: 0;
height: 100%;
overflow-x: visible;
}
.sidebar__inner {
width: 18rem;
height: 100%;
overflow-y: scroll;
}
.sidebar__item {
display: block;
/** transform 관련 속성 생략 **/
}
overflow
의 양 축 활용을 위해 스크롤 컨테이너는 inner
로 감싸서 적용한다.
한 요소에서 x, y축 별로
visible
과hidden, scroll
속성을 따로 설정할 수는 없다. 한 축을 어느 한 속성으로 설정하면 다른 축은 자동으로 같은 속성이 된다.이 때문에
position: fixed
인 상태로width
를 줄이고 item만 x축으로 튀어나오게 만드는 것도 불가능하다.
이렇게만 해도 배경인 sidebar__inner
는 main
뒤에 렌더링되고, sidebar__item
은 main
앞에 렌더링되어 이벤트 막힘을 해결할 수 있다.
sidebar__inner
가 뒤로 렌더링된 이유는 html의 순서 때문이다.
<div id="app" class="app">
<aside class="sidebar">
<div class="sidebar__inner">
<!-- 생략 -->
</div>
</aside>
<main id="main" class="main">
<!-- 생략 -->
</main>
</div>
쌓임 맥락이 없는 요소끼리는 html에서 나타난 순서대로 쌓인다. main
이 sidebar__inner
보다 나중에 선언되었으므로 위에 렌더링된다.
sidebar__item
이 위로 렌더링된 이유는 transform
때문이다.
쌓임 맥락이 생성되는 조건은 다양한데, 그 중 하나가 none
이 아닌 transform
속성이다. 새로운 쌓임 맥락에서 렌더링되기에 sidebar
의 자식 요소임에도 main
위로 렌더링될 수 있다.
전체 조건은 MDN 문서에서 볼 수 있다.
이 상태로 구현하던 와중, main
에 fixed
요소를 사용할 일이 생겼다.
🔽 사이드바 아이템 위로 요소가 겹친 모습.
"히"
자를 보면 흰색 요소가 올라와있다.
위 스크린샷처럼 사이드바 아이템 위로 흰색 그라데이션 요소가 렌더링이 되버렸다. 이는 같은 부모 쌓임 맥락에 기반을 둔 형제 쌓임 맥락끼리는 html 선언 순서에 영향을 받기 때문이다. sidebar__item
의 transform
속성으로 만들어진 쌓임 맥락보다 main
내부 fixed
속성으로 만들어진 쌓임 맥락이 앞으로 온다.
이대로 사용하면 main
요소를 꾸미는데 제약이 생겨 개발이 불편하다. sidebar
를 html에서 가장 아래 순서로 내려 main
과의 간섭을 없에보자.
<div id="app" class="app">
<main id="main" class="main">
<!-- 생략 -->
</main>
<aside class="sidebar">
<div class="sidebar__inner">
<!-- 생략 -->
</div>
</aside>
</div>
.app {
position: relative;
display: flex;
flex-direction: row-reverse;
width: 100vw;
height: 100vh;
}
flex-direction: row-reverse
로 사이드바 자체는 왼쪽에 위치하도록 설정한다.
쌓임 맥락을 만지다가 너무 헷갈린다면 Edge 브라우저 개발자도구 tool 중 3D 보기
의 도움을 받아볼 수 있다.
🔽
3D 보기
로 페이지를 본 모습. 빙글빙글 돌리면서 z축 레이어를 시각적으로 확인할 수 있다.
사실 다른 브라우저의
Layer
tool과 같은 기능이다. 대신 Edge가 제일 이쁘고 부드럽게 보여준다.