Logo

CSS의 z-index 속성 이해하기

우리는 보통 웹페이지를 2차원 공간으로 생각하고 웹 개발을 하는 경우가 많은데요. 하지만 복잡한 웹페이지를 구현할 때는 마치 3차원 공간처럼 요소를 앞뒤로 겹쳐서 배치해야 경우가 생기기 마련이죠.

이번 포스팅에서는 이렇게 웹에서 요소의 Z축 방향의 깊이를 결정하는 CSS의 z-index 속성에 대해서 배워보겠습니다.

z-index가 없을 때 요소 간 상대적 깊이

z-index 속성에 대해서 본격적으로 배우기 전에 먼저 z-index가 없을 때 어떻게 요소(element) 간의 상대적 깊이가 결정되는지에 대해서 이해하는 것이 중요한데요. z-index 속성을 너무 많이 사용하면 스타일 유지보수가 힘들어질 수 있기 때문에 될 수 있다면 z-index 속성을 아예 사용하지 않는 편이 낫기 때문입니다.

기본적으로 z-index 속성이 적용되지 않은 요소 간에는 HTML 문서 상에서 나중에 나오는 요소가 먼저 나오는 요소보다 위로 올라오도록 되어 있는데요. 따라서 다음과 같이 두 개의 div 요소가 겹쳐지면, 두 번째 상자가 첫 번째 상자 위에 올라오게 됩니다.

<div class="first box">1</div>
<div class="second box">2</div>
.first.box {
  background: yellow;
}

.second.box {
  background: tomato;
  margin-top: -50px;
  margin-left: 50px;
}

.box {
  width: 200px;
  height: 200px;
  border: 2px solid;
  font-size: 2rem;
  text-align: center;
  line-height: 200px;
}

하지만 position 속성이 static이 아닌 relativeabsolute, fixed, sticky인 요소가 나타나기 시작하면 겹치는 순서가 바뀔 수 있는데요. 예를 들어, 첫 번째 상자의 position 속성을 relative로 바꿔주면, 두 번째 상자가 밑으로 내려가는 것을 보게 됩니다.

.first.box {
  background: yellow;
  position: relative;  top: 50px;  left: 50px;}
.first.box {
  background: yellow;
  position: relative;  top: 50px;  left: 50px;}

여기서 만약에 제가 두 번째 상자의 position 속성도 relative로 바꿔주면 어떻게 될까요? 그럼 상황이 역전되어 다시 두 번째 상자가 첫 번째 상자 위로 올라오게 됩니다.

.second.box {
  background: tomato;
  position: relative;}

정리를 해보면, z-index가 없을 때 요소 간 상대적 깊이는 HTML 문서 상에서 요소가 나오는 순서와 각 요소의 position 속성이 static이냐 아니냐에 따라서 결정이 되는데요.

  • position 속성이 static이 아닌 요소는 무조건 position 속성이 static인 요소 위로 올라옵니다.
  • position 속성이 static인 요소 간에는 HTML 문서 상에서 나중에 나오는 요소가 먼저 나오는 원소 위로 올라옵니다.
  • position 속성이 relativeabsolute, fixed, sticky인 요소 간에는 HTML 문서 상에서 나중에 나오는 요소가 먼저 나오는 원소 위로 올라옵니다.

z-index가 있을 때 요소 간 상대적 깊이

z-index 속성을 사용하면 위에서 다룬 기본적인 규칙을 무시하고 HTML 문서 상에서 먼저 나온 요소를 나중에 나온 요소보다 앞으로 나오게 할 수 있는데요. 브라우저는 z-index 속성값이 낮은 요소를 먼저 그리고, z-index 속성값이 높은 요소를 나중에 그리기 때문에, 요소가 겹쳐있을 경우 z-index 속성값이 큰 요소가 z-index 속성값이 작은 요소의 일부를 가리거나 전체를 덮을 수 있습니다. (물감을 계속 덫칠한다고 생각하시면 이해가 쉬우실 것 같네요.)

예를 들어, 첫 번째 상자의 z-index 속성을 1로 설정해주면, 두 번째 상자의 앞으로 나오는 것을 볼 수 있습니다.

.first.box {
  z-index: 1;  background: yellow;
  position: relative;
  top: 50px;
  left: 50px;
}

이번에는 두 번째 상자의 z-index 속성을 2로 설정해볼까요? 그러면 두 번째 상자가 첫 번째 상자의 앞으로 나올 것입니다.

.second.box {
  z-index: 2;  background: tomato;
  position: relative;
}

여기서 간과하기 쉬운 부분이 바로 position 속성이 static인 요소에는 z-index 속성이 아무 효력을 내지 못한다는 점입니다. 왜냐하면 position 속성이 static인 요소는 z-index 속성이 auto, 즉 0으로 고정되어 있기 때문입니다.

예를 들어서, 제가 이 상태에서 두 번째 상자에 적용된 position: relative를 제거하면 첫 번째 상자가 다시 앞으로 나오는 것을 볼 수 있습니다.

.second.box {
  z-index: 2;
  background: tomato;
  /* position: relative; */}

또한 z-index 속성은 음수로도 설정해줄 수 있는데요. 이럴 경우, 해당 요소는 브라우저가 가장 먼저 그리기 때문에 심지어 position 속성이 static인 요소보다도 더 뒤에 나타나게 됩니다.

예를 들어, 첫 번째 상자의 z-index 속성을 -1로 설정해주면, 첫 번째 상자가 두 번째 상자 뒤로 들어가는 것을 볼 수 있습니다.

.first.box {
  z-index: -1;  background: yellow;
  position: relative;
  top: 50px;
  left: 50px;
}

정리를 해보면, z-index 속성을 사용하면 position 속성이 static이 아닌 요소의 깊이를 조절할 수 있습니다.

  • position 속성이 static인 요소에는 z-index 속성이 0으로 고정되어 있으며 바꿀 수 없습니다.
  • z-index 속성이 양수로 설정된 요소는 position 속성이 static인 요소보다 앞으로 올라옵니다.
  • z-index 속성이 음수로 설정된 요소는 position 속성이 static인 요소보다 뒤로 내려갑니다.
  • position 속성이 static이 아닌 요소 간에는 z-index 속성값이 클 수록 앞으로 올라오고, 작으면 작을수록 뒤로 내려갑니다. (예: z-index: 1인 요소보다 z-index: 2인 요소가 더 앞에 나오고 z-index: -1인 요소보다 z-index: -2인 요소가 더 뒤로 들어감)

stacking context: z-index가 비교되는 범위

z-index와 관련되서 많은 분들이 오해하시는 부분이 있는데요. 바로 z-index가 HTML 문서 전체 범위에서 비교된다고 생각하는 것이에요. 사실 z-index는 특정 범위 내에서 비교되며, 이것을 CSS에서는 stacking context라고 부릅니다.

예를 들어, 첫 번째 상자의 z-index100이라고 설정하고 두 번째 상자의 z-index2로 설정하면 당연히 첫 번째 상자가 두 번째 상자 앞으로 올라오겠죠? 여기서 첫 번째 상자를 다른 <div> 요소로 감싸고, 그 감싸는 div 요소의 z-index를 한번 1로 줘 볼까요?

<div class="wrapper">  <div class="first box">1</div>
</div><div class="second box">2</div>
.first.box {
  z-index: 100;  background: yellow;
  position: relative;
  top: 50px;
  left: 50px;
}
.second.box {
  z-index: 2;  background: tomato;
  position: relative;
}
.wrapper {
  z-index: 1;  position: relative;
}

그러면 정말 신기하게도 두 번째 상자가 첫 번째 상자 앞으로 올라오는 것을 볼 수 있는데요. 어떻게 z-index 값이 무려 100인 첫 번째 상자가 z-index 값이 겨우 2인 두 번째 상자의 뒤로 내려가게 되었을까요?

비밀은 바로 stacking context에 있는데요. 다시 말해서, 이 두 상자의 z-index 값이 서로 다른 범위에서 비교되기 때문입니다.

첫 번째 상자는 z-index 값이 1<div> 요소에 감싸져있기 때문에, 두 번째 상자의 z-index 값과 직접 비교되지 않습니다. 따라서 첫 번째 상자의 z-index 값이 아무리 크더라도 절대 부모인 <div class="wrapper"> 요소의 외부에 있는 다른 요소의 z-index 값과 경쟁할 수가 없는 것이지요.

다음과 같이 부모 요소에 설정된 z-index 값이 자식 요소의 z-index 값 앞에 붙어있다고 상상하시면 좀 더 이해가 쉬우실 겁니다.

<!-- z-index: 1 -->
<div class="wrapper">
  <!-- z-index: 1.100 -->
  <div class="first box">1</div>
</div>
<!-- z-index: 2 -->
<div class="second box">2</div>

CSS의 stacking context와 관련해서 한 가지 주의할 점은 stacking context의 구조는 반드시 HTML 문서의 DOM 구조와 일치하지는 않는다는 것입니다.

예를 들어, 아래 예제를 보면 두 개의 상자가 모두 여러 개의 <div> 요소로 감싸져 있지만 첫 번째 상자가 두 번째 상자보다 위로 올라오는 것을 볼 수 있는데요. 이것은 첫 번째 상자와 두 번째 상자 간의 z-index 값에 직접적인 비교가 일어났다는 의미죠.

<div>  <div>    <div class="first box">1</div>
  </div></div><div>  <div>    <div class="second box">2</div>
  </div></div>

이번 경우에는 두 상자를 감싸고 있는 <div> 요소에 z-index 속성을 설정해주지 않았기 때문에, 부모 단계에서 stacking context가 형성되지 않았습니다. 따라서 부모 요소가 있든 없든 상관없이 두 상자의 z-index 값이 같은 범위 내에서 비교가 된 것입니다.

<div>
  <div>
    <!-- z-index: 100 -->
    <div class="first box">1</div>
  </div>
</div>
<div>
  <div>
    <!-- z-index: 2 -->
    <div class="second box">2</div>
  </div>
</div>

마치면서

지금까지 CSS에서 z-index 속성을 어떻게 사용하는지에 대해서 여러 가지 예시를 통해서 살펴보았는데요. 꽤 복잡한 알고리즘에 의해서 요소 간의 상대적인 깊이가 결정이 된다는 것을 배우셨을 것입니다.

z-index에 대해서 제대로 이해하고 않고, z-index를 납용하게 되면 여러 요소를 원하는 순서로 겹치는 것이 생각했던 것보다 상당히 골치아파질 수 있는데요. 워낙 악명이 높아서 CSS 커뮤니티에서는 이 문제를 z-index 전쟁(war)이라고 부르기도 합니다.

본 포스팅이 이러한 z-index 문제를 해결하거나 예방하는데 도움이 되었으면 좋겠습니다.