2022-02-23 12:37 작성

Cascade와 inheritance

Table of contents
  1. Cascade와 inheritance
    1. 충돌하는 rules
      1. Cascade
      2. Specificty
      3. Inheritance
    2. 개념들이 어떻게 함께 작동하는지에 대해 이해하기
    3. Inheritance 이해하기
      1. Inheritance 통제하기
      2. 모든 property values를 초기화하기
    4. Cascade 이해하기
      1. Source order
      2. Specificity
      3. !important
    5. CSS 위치 효과
    6. 요약
    7. 저작권

충돌하는 rules

CSS는 Cascading Style Sheets라는 의미를 가지고 있고, 첫 번째 단어 cascading 을 이해하는 것은 엄청나게 중요하다. - Cascade가 행동하는 방식이 CSS를 이해하는 중요한 key다.

Project에 투입 되어 작업할 때, 어떤 부분에서 생각했던대로 적용되어야 할 element styling이 예측과 달리 정상적으로 적용되지 않는 것을 발견할 것이다. 문제는 대개 같은 element에 적용되는 잠재적 rule이 두 가지가 생성되었기 때문일 수 있다. specificitycascade는 충돌이 발생했을 때 어떤 rule을 적용할지 통제하는 mechanisms다. 이 mechanisms에 대한 이해를 통해 예측하지 못한 rule이 적용되는 이유를 알 수 있을 것이다.

또한 여기에서 중요한 개념은 inheritance인데, 몇몇 CSS properties는 기본적으로 근접한 parent element에 설정된 values를 상속한다. 이것 또한 당신이 예측하지 못한 행동을 야기할 수 있다.

우리가 다룰 주요 개념들을 빠르게 훑어보는 것부터 시작해보고, 그 다음 차례차례 어떻게 서로 그리고 당신의 CSS와 상호작용 하는지 볼 것이다. 이 개념들이 이해하기 까다로운 개념일 수도 있다. 그러나 CSS를 작성하는 경험이 늘어나면, 작동하는 방식이 더 자명해질 것이다.

Cascade

Stylesheets cascade - 가장 간단한 수준에서 이것은 CSS rules의 순서가 중요하다는 의미다; 같은 특수성을 가진 두 개의 rules를 적용할 때, CSS에서 가장 마지막에 오는 것이 실제적으로 사용되는 것이다.

아래의 예시에서, h1 element에 적용되는 두 가지 rules가 있다. 여기서 h1은 blue로 색칠될 것이다 - 이 rules는 동일한 selector를 가지고 있어서 같은 특수성(specificity)를 지닌다, 그래서 마지막의 rule이 최종적으로 이기게 되는 것이다.

h1 {
  color: red;
}

h1 {
  color: blue;
}

Specificty

Specificity는 만약 rules가 서로 다른 selectors를 가지고 있으면 browser가 어떤 rule을 적용해야 할지 결정하는 mechanism이다. 이는 기본적으로 selector의 선택이 얼마나 구체적인지에 대한 표시다.

  • Element selector가 덜 구체적 - 한 page 상에 나타나는 모든 특정 elements를 선택 - 그래서 낮은 점수를 가진다.

  • Class selector는 더 구체적 - 한 page 상에서 구체적인 class 속성 value가 있는 elements만 선택 - 그래서 높은 점수를 가진다.

예시 시간! 아래에 h1에 적용될 수 있는 두 가지 rules를 가지고 있다. 그리고 아래의 h1은 red로 색칠된다 - Class selector는 높은 특수성을 가지므로 element selector보다 먼저 선언 되었을지라도 class selector가 적용된다.

.main-heading {
  color: red;
}

h1 {
  color: blue;
}
<h1 class="main-heading">This is my heading.</h1>

Inheritance

Inheritance 또한 이 문맥에서 이해할 필요가 있다 - parent elements에서 설정된 어떤 CSS property values는 child elements로 상속될 수 있다(어떤 것은 그렇지 않음).

예를 들어, 한 element에 colorfont-family를 설정하면, 그 element 안에 있는 모든 element도 직접적으로 color와 font values를 변경하지 않는 이상 같은 color와 font로 styling 될 것이다.

어떤 properties는 상속하지 않는다 - 예를 들어, 만약 당신이 한 element의 width를 50%로 설정한다면 자손들도 부모 width의 50%를 가져오지는 않는다. 만약 그렇게 된다면, CSS는 사용하기 매우 절망적이게 될 것이다.

Note: MDN CSS property reference pages에서 Formal definition이라고 불리우는 기술 정보 box를 찾을 수 있는데, 이 box는 상속이 되는지 여부를 포함하는 property에 대한 몇 가지 data points를 list로 보여준다. 예시로 color property Formal definition section을 보도록 하자.

개념들이 어떻게 함께 작동하는지에 대해 이해하기

Cascade, specificity, 그리고 inheritance, 이 세 가지 개념이 무슨 element에 CSS를 적용할지 함께 통제한다; 아래의 sections에서 어떻게 함께 작동하는지에 대해 살펴볼 것이다. 때로 조금은 복잡해보이지만, CSS에 대한 경험이 쌓일수록 기억할 수 있게 될 것이다. 만약 잊더라도 항상 찾아볼 수 있다는 점을 되새겨라! 심지어 경험이 있는 개발자들도 모든 자세한 내용들을 기억하지는 못 한다.

아래의 동영상은 Firfox DevTools를 이용해 한 page의 cascade, specificity, 그리고 그 이상의 것들을 어떻게 검사할 수 있는지 보여준다.

Inheritance 이해하기

Inheritance(상속)부터 시작해보도록 하자. 아래에는 <ul>이 있고 그 안에는 두 수준의 unordered lists가 내포 돼 있다. 바깥 쪽 <ul>은 border, padding, 그리고 font color를 가지고 있다.

Color는 직접적 children(자손)에 적용될 뿐만 아니라 간접적 자손에도 적용된다 - 아주 가까이에 있는 자손 <li>, 그리고 그 안에 한 번 내포된 자손도 그렇다. 그리고 special이라는 class를 두 번 내포된 list에 추가하고 다른 color를 추가한다. 그럼 그 자손에 class의 styling이 상속된다.

  • Item One
  • Item Two
    • 2.1
    • 2.2
  • Item Three
    • 3.1
      • 3.1.1
      • 3.1.2
    • 3.2
.main {
  color: rebeccapurple;
  border: 2px solid #ccc;
  padding: 1em;
}

.special {
  color: black;
  font-weight: bold;
}
<ul class="main">
  <li>Item One</li>
  <li>Item Two
    <ul>
      <li>2.1</li>
      <li>2.2</li>
    </ul>
  </li>
  <li>Item Three
    <ul class="special">
      <li>3.1
        <ul>
          <li>3.1.1</li>
          <li>3.1.2</li>
        </ul>
      </li>
      <li>3.2</li>
    </ul>
  </li>
</ul>

위에서 언급했던 widths, margins, padding, 그리고 borders는 상속하지 않는다. 만약 list의 자손에 border가 상속된다면 모든 각각의 list와 list item은 border를 얻을 것이다 - 이 부분은 아마 우리가 원하지 않는 기능일 것이다!

어떤 properties가 기본적으로 상속되고 그렇지 않은지는 대체로 일반적인 상식에 근거한다.

Inheritance 통제하기

CSS는 inheritance(상속)을 통제하기 위해 네 가지 특별한 universal property를 제공한다. 모든 CSS property는 이 values를 받아들인다.

inherit: 선택된 element가 parent element와 같도록 property value를 설정한다.

initial: 선택된 element가 해당 property의 초기값이 되도록 value를 할당한다.

unset: Property의 value를 자연스러운 value로 초기화한다, 이는 만약 property가 자연스럽게 inherit처럼 상속하거나 그렇지 않을 경우 initial처럼 행동한다.

revert: 많은 경우에서 unset처럼 행동한다, 그러나 property에 적용되는 기본 value보다 browser에서 기본으로 가지고 있는 styling value를 우선 적용한다.

links list를 보며 어떻게 universal values가 작동하는지 탐색해볼 수 있다.

예를 들어:

  1. 두 번째 list item은 my-class-1이 적용되어 있다. 이것은 value를 상속하여 내포된 <a> element의 색깔에 설정한다.

  2. 아래의 예시에서 왜 세 번째와 네 번째 links의 색깔이 저러한지 이해가 되는가? 세 번째 link는 initial로 설정되어 있어 property의 초기값(이 경우 black)이 할당된다(Browser의 초기값인 blue가 아님). 네 번째는 unset으로 설정되어 있는데, 이는 parent element의 색깔(green)을 사용한다는 의미다.

  3. 만약 당신이 <a> element의 색깔을 새로운 것으로 정의한다면 어느 links의 색깔이 변할까? - 예를 들어 a { color: red; }인 경우, 아래의 예시는 모두 부모로부터의 상속과 관련 돼 있으므로 Default <a> element를 제외하고는 모두 바뀌지 않는다.

body {
  color: green;
}

.my-class-1 a {
  color: inherit;
}

.my-class-2 a {
  color: initial;
}

.my-class-3 a {
  color: unset;
}
<ul>
  <li>Default <a href="#">link</a> color</li>
  <li class="my-class-1">Inherit <a href="#">link</a> color</li>
  <li class="my-class-2">Reset <a href="#">link</a> color</li>
  <li class="my-class-3">Unset <a href="#">link</a> color</li>
</ul>

모든 property values를 초기화하기

CSS shorthand(속기) property인 all은 (거의) 모든 properties에 상속되는 values를 한 번에 적용하는데 사용할 수 있다. 여기에 넣을 수 있는 value는 상속 values 중 어느 것이라도 될 수 있다(inherit, initial, unset 또는 revert). styles의 변동된 부분을 되돌리는 편리한 방법으로 새로이 변경을 시작하기 전, 인지하고 있는 시작점으로 돌아가게 해준다.

아래의 예시에 두개의 blockquotes가 있다. 첫째에는 blockquote element 자체에 적용된 styling이 있고, 둘째에는 class에 all의 value가 unset이 적용되어 있다.

This blockquote is styled

This blockquote is not styled

blockquote {
  background-color: red;
  border: 2px solid green;
}

.fix-this {
  all: unset;
}
<blockquote>
  <p>This blockquote is styled</p>
</blockquote>

<blockquote class="fix-this">
  <p>This blockquote is not styled</p>
</blockquote>

Cascade 이해하기

이제 body에 적용된 CSS가 왜 HTML의 구조에서 깊은 곳에 있는 paragraph에도 적용되는지 이해하게 되었다. 그리고 이전의 서두 수업에서 document의 어느 지점에서 적용된 CSS를 변경시키는 방법에 대해서도 이해하게 되었다(한 element에 CSS를 할당하거나 class를 생성하는 방법을 사용했음). 이제는 하나 이상의 rules가 한 element를 styling 할 경우, cascade가 어떤 CSS rules를 선택할지 알 수 있는 올바른 시선을 가져보도록 할 것이다.

이에 있어 3가지 고려해야 될 요소가 있는데 아래의 순서에 따라 중요도가 증가한다. 나중에 나오는 것(숫자가 높은 것)이 앞선 것(숫자가 낮은 것)을 이기는 rule이 된다.

  1. Source order

  2. Specificity

  3. Importance

위의 요소에 따라 browsers가 특정 CSS를 어떻게 적용하는지에 대해 확인해볼 것이다.

Source order

우리는 이미 cascade에 있어서 source order가 얼마나 중요한지 알아보았다. 만약 무게가 똑같은 하나 이상의 rule이 있다면, 나중에 오는 CSS가 승리한다. 이러한 rules는 element 위에 덮어쓰는 것으로 마지막에 덮어쓰는 것이 결국 승리해 적용된다고 이해할 수 있다.

Specificity

이전에 source order가 중요하다는 사실을 이해했겠지만, stylesheet 상에서 더 늦게 오는 rule임에도 불구하고 일찍 선언된 것과 충돌하여 일찍 선언된 것이 적용되는 상황을 마주할 수 있다. 일찍이 선언된 rule이 앞서는 이유는 더 higher specificity(높은 특수성) 때문이다 - 이는 더 구체성을 가지므로 browser가 element에 styling을 할 때 선택되는 것이다.

이번 수업의 초기에 보았듯, class selector는 element selector 보다 중요성이 더 높기 때문에 class에 정의된 properties가 element에 직접적으로 적용될 것이다.

여기서 주지해야 할 것은 selectors의 rules를 element에 덮어쓰는 것만이 전부가 아니라는 것이다. 오직 같은 properties만 덮어쓸 수도 있다.

이 동작은 CSS에서 반복을 피하는데 도움을 준다. 흔히 basic elements에 generic(포괄적) styles을 정의하고, 다른 classes를 생성한다. 예를 들어, 아래의 stylesheet에서 우리는 2 level(수준) headings에 generic styles를 정의하고 몇가지 properties와 values만 변경한다. 처음에 정의된 values는 모든 headings에 적용되고, 그 후에 classes에서 정의된 구체적 values가 적용된다.

Heading with no class

Heading with class of small

Heading with class of bright

h2 {
  font-size: 2em;
  color: #000;
  font-family: Georgia, 'Times New Roman', Times, serif;
}

.small {
  font-size: 1em;
}

.bright {
  color: rebeccapurple;
}
<h2>Heading with no class</h2>
<h2 class="small">Heading with class of small</h2>
<h2 class="bright">Heading with class of bright</h2>

이제 browser가 specificity를 어떻게 계산하는지 살펴보도록 하자. 하나의 selector가 가지는 specificity의 양은 4개의 다른 values(또는 components)를 사용하여 측정한다. 이는 수천, 수백, 수십, 수개의 단위를 가지며 4개의 숫자로 표현한다(각 자리의 숫자당 1개의 column을 할당해 4자리 숫자로 구성).

  1. Thousands: 선언이 style 속성에 있으면(inline styles라고도 불림), 천의 자리를 한 자리 올린다. 이러한 선언은 selectors가 없으므로 specificity는 항상 1000이 된다.

  2. Hundreds: 모든 selector 중 ID selector가 포함되어 있다면 한 개당 백의 자리를 한 자리 올린다.

  3. Tens: 모든 selector 중 각각의 class selector, attribute selector, 혹은 pseudo-class 당 백의 자리를 한 자리 올린다.

  4. Ones: 모든 selector 중 각각의 element selector 혹은 pseudo-element 당 일의 자리를 한 자리 올린다.

Note: Universal selector(*), combinators(+, >, ~, ‘’), 그리고 negation pseudo-class(:not)은 specificity에 영향을 미치지 않는다.

다음의 표는 감을 잡을 수 있는 예시를 보여준다.

Table of Specificity Score
Selector Thousands Hundreds Tens Ones Total specificity
h1 0 0 0 1 0001
h1 + p::first-letter 0 0 0 3 0003
li > a[href*="en-US"] > .inline-warning 0 0 2 2 0022
#identifier 0 1 0 0 0100
No selector, with a rule inside an element's style attribute 1 0 0 0 1000

/* specificity: 0101 */
#outer a {
    background-color: red;
}
        
/* ✅ specificity: 0201 */
#outer #inner a {
    background-color: blue;
}

/* specificity: 0104 */
#outer div ul li a {
    color: yellow;
}

/* ✅ specificity: 0113 */
#outer div ul .nav a {
    color: white;
}

/* specificity: 0024 */
div div li:nth-child(2) a:hover {
    border: 10px solid black;
}

/* specificity: 0023 */
div li:nth-child(2) a:hover {
    border: 10px dashed black;
}

/* ✅ specificity: 0033 */
div div .nav:nth-child(2) a:hover {
    border: 10px double black;
}

a {
    display: inline-block;
    line-height: 40px;
    font-size: 20px;
    text-decoration: none;
    text-align: center;
    width: 200px;
    margin-bottom: 10px;
}

ul {
    padding: 0;
}

li {
    list-style-type: none;
}            
<div id="outer" class="container">
    <div id="inner" class="container">
        <ul>
            <li class="nav"><a href="#">One</a></li>
            <li class="nav"><a href="#">Two</a></li>
        </ul>
    </div>
</div>

여기에서는 무슨 일이 일어나고 있는 것인지 살펴보자. 우리는 먼저, 예시의 7가지 규칙에만 관심을 가질 것이다. 각각의 규칙은 specificity value를 포함하고 있으며 주석 처리를 통해 그 값이 얼마인지 알 수 있다.

  • 처음 두 개의 selectors는 link의 background color를 styling 하기 위해 경쟁한다 - 두 번째가 승리하며 이에 따라 background color를 blue로 변경한다(Chain 상에서 추가 ID가 존재해 specificity 값이 더 높아짐 - 201 vs. 101).

  • 세 번째와 네 번째 selectors는 link의 text color를 styling 하기 위해 경쟁한다 - 두 번째가 승리하며 이에 따라 text color를 white로 변경한다(Element selector를 한 개 덜 가지고 있음에도 class selector를 가지고 있으므로 더 높은 값을 가짐 - 113 vs. 104).

  • 5-7 selectors는 hover 되었을 때 link의 border를 styling 하기 위해 경쟁한다 - 여섯 번째 selector는 명백히 다섯 번째 selector에 패배하는데 specificity는 23 vs. 24를 가진다(Chain 상에서 element selector를 한 개 덜 가지고 있음). 그러나 일곱 번째 selector가 다섯 번째와 여섯 번째를 모두 이길 수 있다(다섯 번째와 같은 수의 하부 selectors를 가지고 있으나 하나가 class selector로 치환 됨). 따라서 33 vs. 23 그리고 24로 최종 우승한다.

Note: 위의 예시는 이해를 쉽게 하기 위한 대략적인 것이다. 실제에 있어서 각각의 selector type은 더 낮은 specificty level로 덮어 쓰여질 수 없는 각자의 specificity level을 가지고 있다.

specificity를 평가하는 좀 더 적절한 방법은 필요할 때 가장 높은 곳부터 가장 낮은 곳으로 이동하는 식으로 개별의 specificity levels를 점수화하는 것이다.

!important

!important는 위의 계산을 모두 덮는 CSS의 특별한 부분이다. 그러나 이것을 쓸 때는 매우 신중해야 한다. !important는 특정 property와 value를 가장 특수한 것(specific thing)으로 만들어 cascade 상의 일반적 규칙들을 덮어쓴다.

아래의 예시를 살펴보자. 두 개의 paragraphs가 있고, 그 중 하나는 ID를 가지고 있다.

This is paragraph.

One selector to rule them all

<p class="better"> This is paragraph.</p>
<p class="better" id="winning">One selector to rule them all</p>
#winning {
  background-color: red;
  border: 1px solid black;
}

.better {
  background-color: gray;
  border: none !important;
}

p {
  background-color: blue;
  color: white;
  padding: 5px;
}

무슨 일이 벌어지고 있는지 살펴보도록 하자.

  1. 세 번째 규칙의 colorpadding values가 적용된 것이 보일 것이다, 그러나 background-color는 그렇지 않다. 왜 그럴까? 세 가지 모두 적용되어야 하는 것이 맞다. 하지만 늦게 오는 규칙이 일찍 온 규칙을 덮어 쓰기 때문에 적용 되지 않은 것처럼 보이는 것이다.

  2. 위에서는 class selectors가 element selectors 보다 높은 specificity를 가지고 있으므로 이기게 된다.

  3. 두 elements 모두 better이라는 class를 가지고 있다, 그러나 두 번째 element는 winning이라는 id를 가지고 있다. ID는 class 보다 높은 specificity를 가지고 있기 때문에 red background color와 1px black border 모두 두 번째 element에 적용 되어야 한다(많은 elements는 같은 class를 가지고 있으므로 ID selectors를 통해 목표로 하고자 하는 element를 구체화 함). 그리고 첫 번째 element는 class에 명시 된 대로 gray background color, 그리고 no border가 적용 되어야 한다.

  4. 두 번째 element는 red background color를 갖지만 no border 상태다. 왜 그럴까? 왜냐하면 두 번째 규칙에 !important 선언이 있기 때문이다 - border: none 다음에 포함하는 이것은 ID가 더 높은 specificity를 가지더라도 이 border value를 적용하겠다는 의미를 나타낸다.

Note: !important 선언을 덮어 쓰는 유일한 방식은 다른 !important 선언을 source order에서 이후에 오는 같은 specificity!important를 포함하거나 더 높은 specificity를 가진 곳에 포함하는 방법 뿐이다.

!important를 알게 되었으니 다른 사람의 code에서 이것을 발견해도 그 기능을 이해할 수 있을 것이다. 그러나 정말, 절대적으로 이 기능이 필요하다고 생각되지 않는 이상 사용하지 않을 것을 강력히 추천한다. !important는 cascade가 일반적으로 동작하는 방식에 변화를 주기 때문에 CSS에서 발생하는 문제들을 debugging 하기 어렵게 만든다(Stylesheet이 클수록 더 어려워짐).

Core CSS module을 직접 수정할 수 없는 CMS나 정말 어느 style을 적용하고자 하는데 다른 방식으로는 구현할 수 없는 부분을 작업할 때 이 기능을 사용할 수도 있을 것이다. 그러나 피할 수 있다면 피하고, 사용하지 않아도 된다면 사용하지 않도록 하자.

CSS 위치 효과

마지막으로, 어느 한 CSS 선언의 중요성은 무슨 stylesheet이 명시 되어 있는지에 달려 있음을 아는 것은 유용하다 - 사용자들은 custom stylesheets를 사용해 개발자들의 styles를 덮어 쓸 수 있다. 예를 들어, 시각이 손상된 사람들은 모든 web pages의 font size를 두 배로 키워 읽기 쉽게 만들고 싶을 것이다.

요약

충돌하는 선언들(Declarations)은 다음의 순서에 따라 적용되며, 나중에 오는 것은 일찍 오는 것을 덮어 쓴다.

  1. User agent style sheets에서의 declarations(e.g. Browser의 기본 styles - 다른 styling이 설정되지 않을 때 사용 됨).

  2. User style sheets에서의 normal declarations(한 사용자에 의해 설정되는 custom styles).

  3. Author style sheets에서의 normal declarations(Web developers에 의해 설정되는 styles).

  4. Author style sheets에서의 important declarations.

  5. User style sheets에서의 important declarations.

Design을 의도했던 대로 표현하기 위해 User stylesheets를 web developers의 stylesheets가 덮어 쓰는 것이 상식적인 작업일 것이다. 하지만 때로는 사용자가 web developer styles를 덮어 쓸 좋은 이유가 있을 수 있다(상기의 시각이 손상된 이용자와 같은 예처럼). 이럴 때는 !important를 사용해 styles를 조정할 수 있다.

저작권


Mozilla 기여자가 작성한 MDN에 대해는 CC-BY-SA 2.5 라이선스에 따라 사용할 수 있습니다.


크리에이티브 커먼즈 라이선스
이 저작물은 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.