[CSS] 예시를 통한 쌓임맥락 이해

thru·2024년 3월 25일
1

네가 왜 거기서 나와


소개

예전에 z-index가 적용안되는 문제를 겪고 처음 쌓임맥락 글을 읽었을 때 유난히 이해가 안갔다.

🔽 MDN의 쌓임 맥락 설명. 잘 모를 때 보면 순간 아득해진다.

프로젝트에서 사이드바를 구현하면서 쌓임 맥락을 만지다보니 이해가 되어서 포스팅에도 예시를 통해 설명해보려 한다.


목표

스크롤하면 목차가 위치에 맞춰 옆으로 튀어나오는 사이드바를 구현하는 게 목표다.


fixed

사이드바는 뷰포트에 고정되는 요소이니 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는 새로운 쌓임 맥락을 만드는데, 쌓임 맥락은 부모의 쌓임 맥락을 기반으로 존재하기에 하나의 쌓임 맥락 내에서 높낮이가 적용되는 것처럼 보인다.


transform

사이드바의 배경만 main 뒤로 보내려면 일단 sidebarmain이 하나의 쌓임 맥락 안에서 같이 존재해야한다. 일단 fixed를 제거해본다.

.sidebar {
  /** position: fixed; 제거 **/
  /** 생략 **/
}

사이드바가 flex box 내부 요소가 되어버려서 main과 공간을 나누게 된다. widthoverflow-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축 별로 visiblehidden, scroll 속성을 따로 설정할 수는 없다. 한 축을 어느 한 속성으로 설정하면 다른 축은 자동으로 같은 속성이 된다.

이 때문에 position: fixed인 상태로 width를 줄이고 item만 x축으로 튀어나오게 만드는 것도 불가능하다.

이렇게만 해도 배경인 sidebar__innermain 뒤에 렌더링되고, sidebar__itemmain 앞에 렌더링되어 이벤트 막힘을 해결할 수 있다.

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에서 나타난 순서대로 쌓인다. mainsidebar__inner보다 나중에 선언되었으므로 위에 렌더링된다.

sidebar__item이 위로 렌더링된 이유는 transform 때문이다.
쌓임 맥락이 생성되는 조건은 다양한데, 그 중 하나가 none이 아닌 transform 속성이다. 새로운 쌓임 맥락에서 렌더링되기에 sidebar의 자식 요소임에도 main위로 렌더링될 수 있다.

전체 조건은 MDN 문서에서 볼 수 있다.


요소 간섭

이 상태로 구현하던 와중, mainfixed 요소를 사용할 일이 생겼다.

🔽 사이드바 아이템 위로 요소가 겹친 모습. "히"자를 보면 흰색 요소가 올라와있다.

위 스크린샷처럼 사이드바 아이템 위로 흰색 그라데이션 요소가 렌더링이 되버렸다. 이는 같은 부모 쌓임 맥락에 기반을 둔 형제 쌓임 맥락끼리는 html 선언 순서에 영향을 받기 때문이다. sidebar__itemtransform 속성으로 만들어진 쌓임 맥락보다 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로 사이드바 자체는 왼쪽에 위치하도록 설정한다.


3D 보기

쌓임 맥락을 만지다가 너무 헷갈린다면 Edge 브라우저 개발자도구 tool 중 3D 보기의 도움을 받아볼 수 있다.

🔽 3D 보기로 페이지를 본 모습. 빙글빙글 돌리면서 z축 레이어를 시각적으로 확인할 수 있다.

사실 다른 브라우저의 Layer tool과 같은 기능이다. 대신 Edge가 제일 이쁘고 부드럽게 보여준다.

profile
프론트 공부 중

0개의 댓글