CSS의 지루한 부분들까지 사랑하기

저자 : Peter Gasston

CSS의 미래에는 우리를 흥분하게 할만한 것들이 많다. 한편으로는 웹페이지를 레이아웃하는 방식을 혁신할 새로운 방식들이 있고, 다른 한편으로는 곧바로 쓸 수 있는 필터와 셰이더 같은 새로운 그래픽 효과들이 있다. 사람들은 이런걸 좋아한다. 잡지와 블로그 역시 이런 것에 관한 글로 가득차 있다.

하지만 이런 것들이 CSS의 얼굴마담 같은 것이라면, 묵묵히 뒤에서 일하는 이들에게도 관심을 가져줄 때인 것 같다. 바로 선택자, 단위, 그리고 함수 같은 기초적인 구성요소들 말이다. 내가 비록 이것들을 지루한 부분이라고 말하긴 했지만, 사실은 애정을 가지고 하는 말이다. 여러분도 꼭 그런 애정을 가졌으면 좋겠다.

왜 그래야만 하는지 그 이유를 알기 위해 쇼윈도에 진열된 쌔끈한 것들과는 거리가 먼, 어두침침한 실험실에서 여전히 만들어지고 있는 새로운 CSS의 "지루한 부분들"을 둘러보도록 하자. 그 중 일부는 이미 나온지 꽤 지났지만 좀 더 많은 관심을 받을만한 것들이고, 어떤 것들은 최근에야 브라우저에 구현되기 시작했다. 어쨌든 간에 이것들은 모두 우리가 일하는 방식에 혁명적인 변화를 가져다 줄 것이다. 그것이 비록 사소하고 대단한 것은 아닐지라도.

상대적 크기 단위들

똑똑한데다 앞날을 내다보기는 개발자인 당신은 아마도 상대적인 크기인 em이나 %로 작업해본 적이 있을테고 그 문제점도 알고 있을 것이다. 바로 값이 상속되는 속성 때문에 계산기를 두드려야만 할 때가 있다는 점 말이다. 예를 들어 문서에 기본 크기를 지정해 두고 페이지의 다른 모든 부분에 상대적인 크기를 지정하는 방식은 요즘 흔히 사용된다. CSS로 표현해보면 다음과 같을 것이다:

html { font-size: 10px; } p { font-size: 1.4em; }

이 방법은 자식 요소에 다른 폰트 크기를 지정하고 싶을 때까지는 아무런 문제가 없다.

The cat sat on the <span>mat.<span>

만약 span의 글씨 크기를 1.2em으로 줄이고 싶다면 어떻게 할 건가? 계산기를 가져다 1.2를 1.4로 나눈 값을 구해야 한다.

p span { font-size: 0.85714em; }

게다가 이런 문제는 em을 쓸 때만 생기는게 아니다. 마찬가지로 퍼센트를 사용해 유연한 웹사이트를 만들 때 그 퍼센트라는 것이 컨테이너에 상대적이기 때문에, 부모의 75%인 요소 안에 부모의 40% 길이를 설정하고 싶은 요소를 53.33333%로 설정해야만 한다. 아주 좋은 방법은 아니다.

'최상위-상대적'인 길이

이제 우리는 rem(root em)을 사용해 이런 문제점을 해결할 수 있다. 여전히 상대적이긴 하지만 이 값은 언제나 고정된 바탕 크기 - 최상위 요소의 크기. 즉, HTML 문서에서는 html요소 - 에 상대적이다. 위의 예제에서와 마찬가지로 최상위 요소에 10px의 글씨 크기가 설정되어 있을 경우 아래와 같이 CSS를 쓸 수 있다.

p { font-size: 1.4rem; } p span { font-size: 1.2rem; }

두 규칙 모두 최상위 글씨 크기에 상대적이기 때문에 훨씬 우아하고 쉽게 쓸 수 있다. 특히 최상위 크기가 10px이나 12px처럼 단순한 경우에 더 편하다. 마치 px 단위를 사용하는 방식으로 돌아간 것 같지만 em을 사용할 때처럼 크기 변경에 자유롭다.

rem 단위는 이 글에 소개한 것 중 가장 잘 지원되는 편에 속한다. IE9를 포함한 모든 최신 브라우저에서 작동하지만 Opera Mobile에는 빠져 있다.

뷰포트-상대적 길이

rem 단위가 멋지다고 생각한다면 퍼센트로 인한 문제를 해결하도록 도와줄 새 크기 단위들 역시 마음에 들 것이다. 이것들은 rem과 비슷하지만 문서의 최상위에 사용자가 지정한 값에 상대적인게 아니라 기기의 뷰포트 크기에 상대적이라는 점이 다르다.

우선 vh와 vw가 있다. 이 둘은 각각 뷰포트의 높이와 넓이에 상대적이다. 둘 다 하나의 숫자를 값으로 가지며, 그 숫자는 퍼센트를 나타낸다. 내가 시나리오 학교에서 배운대로, 말 보다는 직접 보여주도록 하겠다.

div { height: 50vh; }

이 예제에서, 이 div의 높이는 정확하게 뷰포트의 절반이 된다. 1vh는 뷰포트 높이의 1 퍼센트이기 때문에 50vh는 뷰포트의 높이의 50 퍼센트이다.

뷰포트의 크기가 바뀌면 이 단위의 값도 같이 바뀐다. 하지만 퍼센트와는 달리 이 단위를 쓰면 부모 요소의 크기에 대해서 신경쓰지 않아도 된다. 부모 요소의 넓이가 바뀌더라도 10vw로 설정한 요소는 항상 같은 넓이를 가질 것이다.

vmin 이라는 단위도 있다. 이것은 vh나 vw 중 더 작은 값을 뜻한다. 그리고 최근 vmax라는 단위도 스펙에 추가될 것이라고 발표된 바 있다. (이 글을 쓰는 시점에는 아직 추가되지 않았다).

이 단위들은 IE9 이상 그리고 크롬과 사파리 6에서 지원한다.

계산되는 값들

흐르는 혹은 반응형 레이아웃을 작업할 때 크기는 퍼센트로 지정하되 고정된 여백을 가진 그리드 같은 것이 필요한 적이 분명 있었을거다. 예를 들어:

div { margin: 0 20px; width: 33%; }

만약 패딩과 보더만 사용한다면 box-sizing을 통해 문제를 피할 수 있지만 마진을 써야 한다면 소용이 없다. 더 좋은 그리고 유연한 방법은 바로 calc() 값 함수를 사용하는 것이다. 이것을 사용하면 서로 다른 단위간의 계산을 할 수 있다. 예를 들어:

div { margin: 0 20px; width: calc(33% - 40px); }

이 방법은 width에만 쓸 수 있는게 아니다. 길이 값이 허용되는 곳이라면 어디든지 쓸 수 있다. 심지어 calc() 안에 calc()를 쓸 수도 있다.
IE9 이상에서 지원하고(놀랍게도!) 파이어폭스에서는 앞에 접두어 -moz-를 붙여 사용한다. 버전 16이나 17에서는 -moz- 없이도 지원할 것이다. 크롬과 사파리에서는 앞에 -webkit-을 붙여서 쓴다. 하지만 모바일 WebKit에서는 아직 지원하지 않는 것 같다.

일부 문자열만 불러오기

빠른 성능은 늘 중요했다. 하지만 네트워크 속도가 불안정한 모바일 환경은 수많은 기기가 경쟁하는 시장에서 성능을 더욱 중요한 요소로 만들었다. 페이지 로딩을 빠르게 하는 방법 중 하나는 외부에서 불러오는 파일의 크기를 줄이는 것이다. 새롭게 추가된 @font-face 속성이 이 작업을 도와줄 것이다.

이 속성에 유니코드 범위를 넘겨주면 외부에서 폰트파일을 가져온 다음 모든 문자셋이 아니라 지정한 문자들만 로딩한다. 아래의 코드는 foo.ttf에서 단 세 개의 문자만 읽어들이는 예제다.

@font-face { font-family: foo; src: url('foo.ttf'); unicode-range: U+31-33; }

이 방법은 폰트 아이콘에서 페이지에서 사용할 부분만 읽어들이고자 할 때 특히 유용할 것이다. 내가 해본 테스트에서 이렇게 유니코드 범위를 지정해보니 한 폰트 파일을 읽어들일 때 0.85초가 절약됐는데 이는 간과할만한 차이가 아니다. 물론 환경에 따라 수치는 다를 것이다.

이 속성은 IE9 이상, 그리고 크롬과 사파리 등 웹킷 계열의 브라우저에 구현되어 있다.

새로운 의사-클래스

(위에 소개한) 단위와 값들도 좋지만, 나는 선택자와 의사-클래스들에 특별히 관심이 간다. 마치 내가 장인이라도 된 것처럼 느끼게 해주는 이 똑똑한 선택자들은 왠일인지 잘 알려져있지 않다. 스티브 잡스의 아버지의 말을 빌리자면 "아무도 네가 만든지 모르더라도 담장의 뒷부분도 앞쪽과 마찬가지로 보기좋게 만들어야 한단다. 왜냐하면 너는 알고 있으니까".

처음 :nth-of-type()을 써봤을 때 그건 정말 하나의 계시에 다름 아니었다. 마치 지각의 문을 열어제낀 기분이었다. 음, 좀 오바했나? 어쨌든 새로운 CSS 의사-클래스 중 정말 눈여겨 볼만한 것들이 있다.

부정 의사-클래스

:not()이 얼마나 유용한지를 써보기 전에는 모를 것이다. :not()에는 선택자 하나를 인자로 넘긴다. 여러개를 조합해 쓸 수는 없다. 일단 한 선택자로 선택된 요소들 중에서 :not()에 매치되는 요소들은 제외된다. 복잡하게 들릴지 모르지만 실제로는 아주 간단하다.
상상해 보자: li 요소들 중에 홀수번째 요소에만 어떤 규칙을 적용하고 싶다. 그런데 맨 마지막 요소는 항상 제외해야 한다. 이럴 때 보통은 다음과 같이 할 것이다.

li { color: #00F; } li:nth-child(odd) { color: #F00; } li:last-child { color: #00F; }

하지만 이 부정 의사-클래스를 사용하면 :last-child를 인자로 넘겨 마지막 요소를 제외할 수 있다. 그렇게 함으로서 규칙의 수를 줄여 코드를 조금이라도 더 관리하기 쉽게 만들 수 있다.

li { color: #00F; } li:nth-child(odd):not(:last-child) { color: #F00; }

:not()이 없더라도 다른 방법으로 해결할 수 있는, 어찌보면 대단한 것은 아닐지 모르지만 분명히 유용하다. 내장 웹킷 뷰를 사용한 한 프로젝트에서 이것을 사용했었는데 유용했다. 내가 가장 좋아하는 의사-클래스 중 하나다.

흠. 맞다. 내가 특별히 좋아하는 의사 클래스들이 있다.

이 의사-클래스는 본 글에서 소개한 것들 중 가장 널리 구현된 기능이다. IE9 이상 그리고 모든 최신 브라우저에서 접두어 없이 사용할 수 있다. jQuery를 써봤다면 :not()과 이미 친숙할지도 모르겠다. jQuery 1.0부터 이미 이것과 비슷한 not() 메소드가 있었으니까.

어느것과도 매치되는 의사-클래스

:matches() 의사-클래스는 한 개의 선택자/선택자의 조합/콤마로 구분된 (선택자의) 목록/ 혹은 이 모든 것의 조합을 인자로 받는다. 대단하다! 흠, 그런데 뭐에 쓰는거지?

이 의사-클래스는 손도 대기 싫을 정도로 복잡한 선택자들을 정리할 때 유용하다. 예를 들어 서로 다른 html 태그 안에 있는 p 중 일부만 선택하고 싶으면 아마 이렇게 할 것이다:

.home header p, .home footer p, .home aside p { color: #F00; }

:matches()를 사용하면 선택자들의 공통점을 이용해 더 짧게 만들 수 있다. 위의 예제에서 모든 선택자가 .home으로 시작해 p로 끝나고 있으니 :matches()에 둘 사이에 있는 것들을 모을 수 있다. 무슨 말인지 모르겠다고? 아래를 보자.

[cci_css].home :matches(header,footer,aside) p { color: #F00; }[/cci_css]

사실 이건 CSS4(더 정확하게는 CSS 선택자 레벨 4)인데 스펙에 따르면 :not()과 마찬가지로 콤마로 구분된 복합 선택자들도 사용할 수 있게 될 예정이라고 한다. 오예!

현 시점에서 :matches()는 크롬과 사파리에서 접두어 -webkit- 을 붙여야 하고, 파이어폭스에서는 예전에 사용하던 이름인 :any()에 -moz- 접두어를 붙여 써야 한다.

아직도 "일하는 말"이 좋아지지 않았는가?

("일하는 말"이란 화려한 겉모습 보다는 묵묵하게 실제로 일을 하는 무언가를 뜻합니다.)

이 글에서 소개한 새 기능들은 무엇보다 사소하지만 성가신, 반복되는 선택자 작업에서부터 최근 맞닥뜨리게 되는 반응형 웹사이트 제작에서의 어려움에 이르기까지 실전에서의 문제를 해결해 준다는 점이 훌륭하다. 사실 이 모든 기능들이 일상적으로 쓰일만한 것들이다.

필터와 같은 새 기능들이 눈에 더 잘 띄겠지만 여기에 소개한 것들이이말로 여러분의 모든 프로젝트에 쓰일 수 있을만한 것들이다.

매일의 작업을 더 쉽게 해주고, 더 많은 것을 할 수 있도록 해주는 이런 것들이 지루하다고 할 수 있을까?

Translated with the permission of A List Apart Magazine and the author[s]. / 이 글은 A List Apart Magazine과 원저자의 허락 하에 제가 번역한 것입니다. 원문은 Learning to love the Boring Bits of CSS입니다. 공동 번역 플랫폼인 Looah에서도 이 글을 읽거나, 번역에 참여할 수 있습니다.

About Sanghyun Park

a.k.a Baxang. Software engineer lives in Sydney, Australia born in Seoul, South Korea.