Module CSS

많은 사람들이 HTML을 문맥에 맞게 작성하는 데에는 많은 시간을 들이지만 CSS를 효율적으로 사용하는 데에는 그렇게 많은 시간을 들이지 않는 듯 보인다.

특히 SASS를 이용하면서 이런 현상은 극에 달해, 단순하게 상속을 이용하거나 클래스의 중첩을 이용해 더 간단히 작성할 수 있지만, 이를 각 클래스에 넣는 행태를 근래에는 부쩍 많이 보고있다.

CSS를 사용함에 있어서 '잘 나오면 장땡'이라는 자세는 옳지 않다고 생각하고, SASS를 사용하면 코드의 관리측면이나 효율성 측면에서 좋지만, 이 글에서는 그런 것들을 잠시 내려두고 약간 원론에 가까운 이야기를 하려고 한다.

CSS Cascading

CSS는 부모가 자식에게 영향을 준다. 특히 color, font 등이 주로 자식에게 영향을 주며, 이런 속성들은 대체로 <body>에 작성해두어 모든 요소들이 영향을 받게 한다.

아래의 간단한 코드 조각을 살펴보자. 아래 코드는 <body> 요소 안의 모든 요소의 폰트 크기는 2em으로 행간은 3em으로, 그리고 폰트 패밀리는 APPLE SD Gothic NEOsans-serif로 바꾼다.

body {
  font-size: 2em;
  line-height: 3em;
  font-family: "APPLE SD Gothic NEO", sans-serif;
}

이 때 영문은 Helvetica로 보이고 싶고, 한국어는 APPLE SD Gothic NEO로 보이고 싶다면, font-family 속성의 특징 중 하나인, 앞에 있는 font로 나타낼 수 없다면 뒤에 있는 font로 나타낸다는 특징을 이용해 더 쉽게 폰트를 지정할 수 있다.

body {
  font-size: 2em;
  line-height: 3em;
  font-family: "Helvetica", "APPLE SD Gothic NEO", sans-serif;
}

<input> 요소에서 font-sizeline-height가 상속받지 않고, <a> 요소에서 color 속성을 상속받지 않는데, 그런 경우에는 inherit 키워드를 명시적으로 작성해주어 상속받게 할 수 있다.

a {
  color: inherit;
}
input {
  font-size: inherit;
  line-height: inherit;
}

그렇게 하면 굳이 여러군데에 불필요한 CSS를 추가하지 않더라도 한번에 여러 CSS를 정의할 수 있다. 원래 CSS는 이렇게 효율적으로 사용할 수 있는 언어이다.

셀렉터 우선순위

CSS 셀렉터에는 우선순위가 있다. 일반적으로는 class selectortype selector보다 높다. 절대적으로 어떤 게 높다 낮다 할 수는 없지만, CSS 적용 잘 안된다고 !important를 남발하는 행위만 안했으면 좋겠다.

combinator가 하나 추가될 때마다 일반적으로 우선순위가 높아진다. 아래의 코드에서 <p> 요소의 색상은 어떻게 나올 지 유추해보자.

.log {
  color: yellow;
}
p {
  color: red;
}
section p {
  color: purple;
}
<section>
  <p class="log">이 p요소의 색상이 어떻게 나올까요?</p>
</section>

결론부터 말하면 위 예제에서 <p> 요소의 색상은 yellow로 나온다. class selectortype selector보다 우선순위가 높고, section ptype selector가 단순히 두개 있는 형태이기 때문에, .log가 우선순위가 더 높아지는 것이다.

셀렉터의 우선순위를 계산하는 방법은 아래와 같다.

셀렉터 레벨 3 한국어 번역본 9장에서 발췌

부정 의사 클래스 안의 셀렉터도 다른 셀렉터와 동일하게 셉니다. 그러나, 부정 의사 클래스 자신은 의사 클래스로 세지 않습니다.
3개의 숫자를 결합한 게 셀렉터의 상세도입니다. (large base의 숫자 시스템을 기반으로 합니다)

*               /* a=0 b=0 c=0 -> specificity =   0 */
LI              /* a=0 b=0 c=1 -> specificity =   1 */
UL LI           /* a=0 b=0 c=2 -> specificity =   2 */
UL OL+LI        /* a=0 b=0 c=3 -> specificity =   3 */
H1 + *[REL=up]  /* a=0 b=1 c=1 -> specificity =  11 */
UL OL LI.red    /* a=0 b=1 c=3 -> specificity =  13 */
LI.red.level    /* a=0 b=2 c=1 -> specificity =  21 */
#x34y           /* a=1 b=0 c=0 -> specificity = 100 */
#s12:not(FOO)   /* a=1 b=0 c=1 -> specificity = 101 */

결론은 CSS Selector의 조합에 따라서 셀렉터의 우선순위가 바뀔 수 있으며, 셀렉터의 우선순위를 잘 지정한다면 한 셀렉터를 다방면으로 효율적이게 활용할 수 있게 된다.

기초 스타일과 Theme

단순한 CSS Snippet이라면 기본 스타일과 Theme를 분리할 필요가 없을 수도 있지만, UI 복잡도가 높아지면 높아질 수록 웹사이트 전체의 기본 색상과 포인트 색상, 모듈별 색상을 분리하기 조금씩 어려워진다.

그래서 나는 처음에 Default Style을 작성해둔 후에, 그 위에 Theme를 씌운다는 느낌으로 작업을 많이 한다.

OOCSS에서는 이를 structure와 skin의 구분이라고 표현하는데, 개인적으로 CSS에서 structure와 skin은 결국 유기적일 수밖에 없다고 생각하기 때문에, 추상화시킨 클래스를 조합하되, 추상화 이전의 CSS가 어느정도는 기본셋을 가지도록 한다.

테마 영역에서 currentColor 키워드를 사용하면 조금 더 쉽게 코드를 작성할 수 있지만, currentColor 키워드는 브라우저 지원율이 그렇게 높지 않기 때문에 실제로 사용하기 어려울 것이다. http://caniuse.com/#feat=currentcolor에서 지원율을 확인할 수 있다.

따라서 이 글에서는 currentColor 키워드는 사용하지 않고, 위에서 설명했던 셀렉터의 우선순위를 활용해서 교묘하게 style을 제어할 예정이다.

여기 간단한 버튼을 만드는 예제가 있다. 예제는 btns.html에서 확인할 수 있다.

.btn {
  display: inline-block;
  padding: 2px 6px;
  border: 1px solid gray;
  color: gray;
  background: transparent;
  border-radius: 1em;
  cursor: pointer;
}

위 CSS로 작성한 버튼은 회색 외곽선에 이 버튼을 배경은 없지만 컬러와 외곽선이 동일한 색상인 버튼, 배경과 외곽선은 동일한 색상이지만 컬러가 다른 버튼, 컬러는 동일하지만 배경과 외곽선이 없는 버튼의 3가지 형태로 만들려고 한다.

.btn {
  display: inline-block;
  padding: 2px 6px;
  border: 1px solid gray;
  color: gray;
  background: transparent;
  border-radius: 1em;
  cursor: pointer;
}
.btn.btn_filled {
  color: #fff;
}
.btn.btn_borderd {
  background: transparent;
}
.btn.btn_link {
  border: none;
  background: transparent;
}
.btn_red {
  color: red;
  background: red;
  border-color: red;
}

위 버튼 예제는 예제만을 위한 CSS 셀렉터이며,
실제로 셀렉터를 작성하실 때는 컴포넌트의 의미와 활용성에 맞게 네이밍해야 한다.

위 예제에서 .btn은 기본 버튼 스타일을 담당한다. 간단히 회색으로 들어간 외곽선과 같은 색상의 폰트, 그리고 배경은 흰색일 것이다. .btn_red는 다시 정의해야하는 테마 전체를 다 재정의한다. color, background, border-color가 테마에 따라서 바뀔 속성들이기 때문에 그렇게 작성해준다.

.btn.btn_filled, .btn.btn_borderd, .btn.btn_link는, 두개의 클래스 셀렉터가 결합되어있는 형태이기 때문에 .btn_red보다 우선순위가 높다. 즉 위 세개의 셀렉터가 각 스타일에서 초기화시켜줘야하는 CSS를 재정의해준다. 이렇게 작성해주면 결과가 예제사이트에 있는 것처럼 모두 표현할 수 있게 된다.

여기에서 이제 테마에 따른 클래스만 계속 추가해주면, 원하는 형태의 조합을 추가할 수도 있으며, 혹은 변경해 줄 수 있다. 테마를 관리하는 게 굉장히 심플해진다.

이는 컬러뿐만 아니라 button의 형태 (radius가 들어갔는지 그렇지 않은 지), button의 사이즈, button의 grouping등 여러 형태에서 유용하게 사용할 수 있다.

결론

나는 이러한 형태의 CSS를 설계할 때 3개의 원칙을 가진다.

위 세가지 원칙 모두 재사용성의 문제이다. CSS는 남용하기 쉬운 언어이며 남발하기 쉬운 언어다.

글의 초반에도 말했듯이 '잘 나오면 장땡이다'라는 마인드로 개발하다보면, CSS의 양이 불필요하게 많아질 수 있으며 그는 곧 관리에 영향을 미치고, 더 나아가서는 성능을 저하시키는 일도 초래할 수 있다. 재활용할 수 있는 코드는 최대한 재활용하도록 하자.

레이아웃과 컴포넌트를 분리해두면 조금 더 코드를 재활용하기 좋아진다. 보통 나는 이런 작업을 할 때 다음 순서대로 작업한다.

  1. UI 컴포넌트 단위로 개발을 한다.
  2. UI 컴포넌트는 유동적으로 대응을 할 수 있게 하여, 추후에 Layout에서 width를 정할 때 어디에 들어가더라도 유기적으로 돌아가게 한다.
  3. 레이아웃을 잡는다. width를 여기서 정한다.
  4. UI컴포넌트를 레이아웃에 넣는다.
  5. 테스트

여담

아마 처음 CSS 설계를 하는 사람이라면 어떻게 분리를 해야할 지 모를 수도 있고, 어떤 순서대로 CSS를 설계해야하는 지 모를 수도 있다.

이럴 때 나는 보통 SMACSS나 BEM 등을 참고하기도 하고, 이미 잘 구현된 구현체(Bootstrap 같은)를 참고하기도 한다.

다만 CSS 설계에 대해서 작업을 할 때는, 되도록이면 SCSS의 사용보다 CSS를 사용하여 Selector의 우선순위나 Selector의 조합을 조금 더 면밀히 살펴보는 게 좋다.

SCSS를 이용하면 코드의 생산성이 높아지는 건 사실이다. 다만 생산성의 추구와 효율적인 CSS는 다른 이야기이기 때문에, 이 글을 보고 한번정도는 직접 CSS를 설계해봤으면 하는 바램이다.