SMACSS - 유연하고 모듈화된 CSS 방법론 학습 - 2. 상태 바꾸기, 적용 깊이(Depth of Applicability), 선택자의 성능, HTML5와 SMACSS
상태 바꾸기
클래스 이름으로 바꾸기
버튼을 눌러서 요소를 숨기는 방법.
$('.btn-close').click(function(){
$(this).parents('dialog').addClass('is-hidden');
});
버튼을 눌러서 메뉴를 보이는 방법(버튼을 누르면 버튼이 눌린 상태가 되고, 메뉴는 보이는 상태가 된다) 세가지.
작명 규칙(naming-convention)을 활용한 방법
마크업
<div id="content">
<div class="toolbar">
<button id="btn-new" class="btn" data-action="menu">New</button>
</div>
</div>
<div id="menu-new" class="menu">
<ul>...</ul>
</div>
data-action
어트리뷰트가 어떤 자바스크립트 코드를 호출할지 결정한다.-
버튼을 클릭하면 버튼의 아이디를 토대로 메뉴를 찾는다.
$('#btn-new').click(function(){ $(this).addClass('is-pressed'); $('#menu-' + $(this).id.substr(4)).removeClass('is-hidden'); });
메뉴와 버튼을 이웃하게 하기
마크업
<div id="content">
<div class="toolbar">
<button id="btn-new" class="btn" data-action="menu">New</button>
<div id="menu-new" class="menu".
<ul>...</ul>
</div>
</div>
</div>
-
div.toolbar
에 ‘.is-active’를 추가하고 스타일을 매기는 방법.is-active .btn { color: #000; } .is-active .menu { display: block }
메뉴와 버튼이 같은 요소 안에 있어야만 하고, 다른 항목이 툴바에 들어가도 안된다.
-
형제 선택자(
+
)를 이용하는 방법.btn.is-active { color: #000; } .btn.is-active + .menu { display: block; }
여전히 HTML 구조에 의존한다. 언제건 버튼과 메뉴가 이웃한다는 일관성을 지킬수만 있다면야 상관 없다.
“버튼 메뉴”를 위한 위 규칙들은 어디에서 선언해야 할까? 버튼? 메뉴? 버튼에 다는 게 낫다.
어트리뷰트 선택자로 상태 바꾸기
이점
- 레이아웃 클래스나 모듈 클래스와 구분돼 보인다.
- 여러가지 상태로 쉽게 전환할 수 있다.
예시:
.btn[data-state=default] { color: #333; }
.btn[data-state=pressed] { color: #000; }
.btn[data-state=disabled] { opacity: .5; pointer-events: none; }
HTML5는 data-
로 시작하는 어트리뷰트를 허용한다.
CSS 애니메이션이 있는 클래스 기반 상태 변화
딜레마: CSS는 모습, 자바스크립트는 페이지의 작동을 규정한다. 그러나 애니메이션은 주로 자바스크립트로 처리돼왔다.
나타났다 사라지는 메시지의 예:
JS:
function showMessages (s) {
var el = document.getElementById('message');
el.innerHTML = s;
el.className = 'is-visible';
setTimeout(function() {
el.className = 'is-hidden'
}, 3000);
}
CSS:
@keyframes fade {
0% { opacity:0; display:block; }
100% { opacity:1; display:block; }
}
.is-visible {
display: block;
animation: fade 2s;
}
.is-hidden {
display: none;
admination: fade 2s reverse;
}
자바스크립트는 상태를 바꾸고, 상태에 따른 모습은 CSS가 담당한다. 그러나 많은 브라우저가 이를 구현해주지 않는다. 다행히도 CSS3 애니메이션 권고에 따라 브라우저들이 바뀌고 있는 중이다. 당분간은 윗 부분을 다음과 같은 방식으로 써야 하고, is-removed 클래스를 추가해야한다.
@-webkit-keyframes fade {
...
}
...
.is-removed { display: none; }
자바스크립트는 다음과 같이 쓴다.
function showMessages (s) {
var el = document.getElementById('message');
el.innerHTML = s;
el.className = 'is-visible';
setTimeout(function() {
el.className = 'is-hidden'
setTimeout(function(){
el.className = 'is-removed';
}, 2000);
}, 3000);
}
의사(pseudo) 클래스 이름으로 상태 바꾸기
예시:
.btn { background-color: #333; }
.btn:hover { background-color: #336; }
.btn:focus { box-shadow: 0 0 3px rgba(48,48,96,.3); }
하위 모듈을 만들시 규칙을 다시 맞춰서 써야할 수도 있다.
.btn { background-color: #333; }
.btn:hover { background-color: #336; }
.btn:focus { box-shadow: 0 0 3px rgba(48,48,96,.3); }
.btn-special { background-color: #DEDB12; }
.btn-special:hover { background-color: #B8B50B; }
여기에 클래스로 상태를 나타내면 더 복잡해진다.
.btn { ... }
.btn:hover { ... }
.btn:focus { ... }
.btn-special { ... }
.btn-special:hover { ... }
.btn.is-pressed { ... }
.btn.is-pressed:hover { ... }
.btn-special.is-pressed { ... }
.btn-special.is-pressed: hover { ... }
미디어 쿼리
예시:
@media screen and (max-width: 400px) {
#content { float: none; }
}
SMACSS에서는 특정 모듈에 적용되는 스타일을 분리해야하므로, 분기점을 하나만 만들기보다는 미디어 쿼리를 모듈의 상태와 함께 둬야한다.
.nav > li { float: left; }
@media screen and (max-width: 400px) {
.nav > li { float: none; }
}
미디어 쿼리 선언을 여러번 해야할테지만, 모듈에 대한 정보를 한 곳에 다 모을 수 있다. 그러면 테스트하기에도 좋고, 모듈화해서 템플릿과 CSS를 따로 불러와도 된다.
적용 깊이(Depth of Applicability)
뜻: 규칙에 영향을 받는 세대의 수. “body.article > #main > #content > #intro > p > b
“는 6. 같은 페이지에서 “.article #intro b
“도 6.
적용의 깊이가 크면 HTML 구조에 더 많이 의존하게 된다. 그러면 컴포넌트를 이동시키기 어려워진다. 사이드바에 있는 모듈을 푸터에도 만드려는 경우:
#sidebar div, #footer div { border: 1px solid #333; }
#sidebar div h3, #footer div h3 { margin-top: 5px; }
#sidebar div ul, #footer div ul { margin-bottom: 5px; }
이 모듈의 루트는 div
요소이고, 여기서 모듈을 만들면 된다.
.pod { border: 1px ... }
.pod > h3 { ... }
.pod > ul { ... }
-
모듈을 쓸 때마다 클래스에
pod
를 써줘야 하지만, 이렇게 하면 적용 깊이를 줄일 수 있는 이점이 있다. 그러면 동적 콘텐츠를 위한 템플릿을 자유자재로 쓸 수 있다.<div class="pod"> <h3></h3> <ul> \{\{#items}} <li></li> </ul> </div>
-
더 유연한 구조가 필요치 않다면,
h3
나ul
에 클래스를 매길 필요는 없다. 그러나 더 유연하려면 반드시 클래스를 매겨야 한다. 가령ul
대신에ol
,div
가 들어가는 경우:.pod > ul, .pod > ol, .pod > div { ... }
이렇게 스타일을 매길 수도 있지만, 클래스를 붙이면 다음과 같이 축약할 수 있다.
.pod-body { ... }
선택자의 성능
Steve Souders의 연구에 따르면 CSS 선택자의 성능은 기껏해야 0.05초 밖에 나지 않는다. 그러므로 너무 신경쓸 필요는 없지만 알아두긴 하자.
선택자는 오른쪽부터 계산된다. .module h3
의 경우 브라우저는 페이지 내에 있는 모든 h3
를 먼저 찾아낸다. 그 다음 .module
에 속한 것을 찾는다. 그러므로 오른쪽에 요소 선택자를 두면 성능이 떨어진다. 그래서 구글에서는 모든 요소에 클래스를 붙여 쓰지만, 저자는 실용적으로 접근하자고 한다.
HTML5와 SMACSS
HTML5이든 HTML4이든 SMACSS가 효과적인 이유
- HTML 페이지에 말뜻(semantic value)을 더해준다.
- HTML 구조에 덜 의존한다.
HTML5에는 좀더 말뜻이 분명한 요소가 추가됐다.(e.g. section
, header
, aside
)
그러나 태그가 모듈을 대체할 수는 없다. 다음 nav
가 같은 nav
일까?
<nav class="nav-primary">
<h1>Primary Nav</h1>
<ul>...</ul>
</nav>
<nav class="nav-secondary">
<h1>External Links</h1>
<ul>...</ul>
</nav>
nav.nav-primary
는 페이지 상단에 있는 수평 네비게이션이겠지만, nav.secondary
는 아마 사이드바에 들어갈 네비게이션 같다.
nav.nav-primary li { display: inline-block; }
nav.nav-secondary li { display: block; }
근데 클라이언트가 와서 수평 메뉴에 수직 메뉴를 추가해달라고 한다.
<nav class="nav-primary".
<h1>Primary Nav</h1>
<ul>
<li>About Us
<ul>
<li>Team</li>
<li>Location</li>
</ul>
</li>
</ul>
</nav>
그럼 하위 메뉴에 있는 li
가 수평으로 나열되지 않게 하려면?
ul
을<nav class="nav-secondary">
요소로 감싼다.-
다음과 같이 CSS를 고친다.
nav.nav-primary li { ... } nav.nav-secondary li, nav.nav-primary li li { display: block; }
-
nav
선택자를 버리고 클래스에만 의존한다.<nav class="l-inline"> <h1>Primary Nav</h1> <ul> <li>About Us <ul class="l-stacked"> <li>Team</li> <li>Location</li> </ul> </li> </ul> </nav> .l-inline li { display: inline-block; } .l-stacked li { display: block; }
이게 SMACSS 스타일. 한 술 더떠서 이 레이아웃 규칙을 더 일반화할 수 있다(선택자의 성능은 희생되겠지만):
.l-inline > * { display: inline-block; }
.l-stacked > * { display: block; }