4.3 Sass - CSS Extensions

Nesting, import, extend, 조건과 반복, Mixin, Function

sass-logo

1. Nesting

Nesting은 Sass의 유용한 확장 기능으로 선언을 중첩(nesting)하는 것이다.

CSS는 후손 셀렉터(Descendant combinator)의 경우 부모요소를 기술하여야 한다.

#navbar {
  width: 80%;
  height: 23px;
}

#navbar ul {
  list-style-type: none;
}

#navbar li {
  float: left;
}

#navbar li a {
  font-weight: bold;
}

Sass의 nesting을 사용하면 후손 셀렉터를 간단히 기술할 수 있다. 또한 HTML의 구조를 반영한 CSS를 기술할 수 있다.

#navbar {
  width: 80%;
  height: 23px;

  ul {
    list-style-type: none;
  }

  li {
    float: left;
    a {
      font-weight: bold;
    }
  }
}

너무 깊은 nesting은 가독성을 나쁘게 하고 셀렉터를 복잡하게 만든다.

// Bad case
div#main {
  #sidebar {
    #navbar {
      width: 80%;
      height: 23px;

      aside {
        div {
          ul {
            list-style-type: none;

            li {
              float: left;

              a {
                font-weight: bold;
              }
            }
          }
        }
      }
    }
  }
}

부모 요소의 참조가 필요한 경우 &를 사용한다. 예를 들어, :hover 또는 ::before 등의 가상 클래스 선택자 (Pseudo-class selector)를 지정하는 경우 부모 요소의 참조가 필요하다.

.myAnchor {
  color: blue;

  // .myAnchor:hover
  &:hover {
    text-decoration: underline;
  }

  // .myAnchor:visited
  &:visited {
    color: purple;
  }
}

nesting은 프로퍼티에도 사용할 수 있다.

.funky {
  font: {
    family: fantasy;
    size: 30em;
    weight: bold;
  }
}

위 코드의 트랜스파일링 결과는 아래와 같다.

.funky {
  font-family: fantasy;
  font-size: 30em;
  font-weight: bold;
}

2. @-Rules and Directives

2.1 @import

1개의 CSS 파일에 모든 스타일을 기술하면 유지보수하기 힘들고 가독성이 좋지 않다. 기능에 따라 CSS 파일을 분리하면 재사용 및 유지보수 측면에서 유리하다. 따라서 룰을 정하여 파일을 분리하여 개발하는 것은 효과적인 방법이다.

Sass는 @import directive를 사용하여 분리된 stylesheet 파일을 import할 수 있다. Sass는 기존의 CSS @import보다 편리한 기능을 제공한다.

@import "foo.scss";

// 확장자는 생략 가능하다
@import "foo";

// 여러 개의 파일을 한번에 임포트할 수 있다.
@import "rounded-corners", "text-shadow";

// 변수를 사용해 문자열을 생성하여 임포트할 수도 있다.
$family: "Open+Sans";
@import url("http://fonts.googleapis.com/css?family=#{$family}");

여러 개의 파일로 분할하는 것 또는 분할된 파일을 partial이라 하며 partial된 Sass 파일명의 선두에는 underscore(_)를 붙인다. (_reset.scss, _module.scss, _print.scss)

예를 들어, “_foo.scss”라는 partial된 Sass 파일이 있고 이 파일을 import하는 경우 아래와 같이 기술한다. 파일명 선두의 _와 확장자는 생략할 수 있다.

@import "foo";

partial된 Sass 파일명 선두에 붙인 _의 의미는 import는 수행하되 CSS로의 트랜스파일링은 수행하지 말라는 의미를 갖는다. 따라서 partial은 import시에는 CSS 파일로 트랜스파일링되지 않기 때문에 최종적으로 CSS로 트랜스파일링을 수행할 Sass 파일에서 import한다.

partial

예를 들어, 위 그림과 같이 partial된 _vars.scss, _header.scss, _sidebar.scss, _footer.scss를 style.scss가 import하는 경우를 생각해 보자.

// partial/_vars.scss
$width: 960px;
// partial/_header.scss
#header {
  width: $width;
}
// partial/_sidebar.scss
#sidebar {
  width: $width;
}
// partial/_footer.scss
#footer {
  width: $width;
}
// style.scss
@import "partial/vars";
@import "partial/header";
@import "partial/sidebar";
@import "partial/footer";

_vars.scss에는 변수가 선언되어 있으므로 partial된 _vars.scss, _header.scss, _sidebar.scss, _footer.scss를 import가 수행되어 하나의 파일이 되기 이전에 트랜스파일링을 실행하면 에러가 발생한다. 즉, partial된 Sass 파일명 선두에 붙인 _을 제거하면 에러가 발생한다. 따라서 partial된 Sass 파일명 선두에는 반드시 _를 붙여서 import 시에는 partial이 CSS 파일로 트랜스파일링되지 않고 import가 완료된 이후, CSS로 트랜스파일링을 수행도록 한다.

최신 버전에서는 _을 붙이지 않아도 에러가 발생하지 않는다. @import 대신 @use를 사용하는 방법도 있다. 이에 대해서는 SCSS에 새로 추가된 Module System (@use, @forward)을 참고하기 바란다.

@import는 top-level에서 사용하는 것이 일반적이지만 CSS rule 또는 @media rule 내에 포함시키는 것도 가능하다.

// _example.scss
.example {
  color: red;
}
#main {
  @import "example";
}

위 코드의 트랜스파일링 결과는 아래와 같다.

#main .example {
  color: red;
}

2.2 @extend

기존 스타일을 상속하고자 경우 @extend를 사용한다. 예를 들어, 아래의 경우를 살펴보자.

<div class="error seriousError">
  Oh no! You've been hacked!
</div>

기존에 선언되어 있는 error class를 사용하면서 일부 rule set에 대해서는 다른 선언이 필요한 경우 자주 사용하는 방법이다.

이러한 경우 사용할 수 있는 방법이 상속이다. 상속되는 rule set을 그대로 상속받아 다른 부분만 별도 선언하면 된다.

.error {
  border: 1px #f00;
  background-color: blue;
}

.seriousError {
  @extend .error;

  border-width: 3px;
  border-color: darkblue;
}

위 코드의 트랜스파일링 결과는 아래와 같다. .error와 .seriousError가 공통으로 사용하는 프로퍼티를 묶어 합리적인 룰셋을 생성한다.

.error, .seriousError {
  border: 1px #f00;
  background-color: blue;
}

.seriousError {
  border-width: 3px;
  border-color: darkblue;
}

이제는 하나의 클래스만 적용시키면 된다.

<div class="seriousError">
  Oh no! You've been hacked!
</div>

@extend를 @media 블록과 같이 사용하는 경우, 제대로 작동하지 않는다. 다시 말해, @media 안에서 외부의 선택자를 @extend할 수 없다.

.foo {
  color: red;
}

@media print {
  .bar {
    @extend .foo; // ERROR: You may not @extend selectors across media queries.
  }
}

@extend를 사용하면 트랜스파일링 후 자신의 셀렉터가 어디에 첨부될 것인지 예상하기 어렵고, 예상치 못했던 부작용이 발생할 수 있다. 따라서 @extend의 사용은 가급적 자제하고 Mixin은 사용하는 것을 추천한다.

2.3 Placeholder Selectors

Placeholder Selector는 재사용이 가능한 rule set을 % 키워드로 지정하는 @extend 전용 Selector이다.

Placeholder Selector은 상속만을 위한 rule set으로 자신은 트랜스파일링되지 않는다.

%input-style {
  font-size: 14px;
}

.input-black {
  @extend %input-style;

  color: black;
}

.input-red {
  @extend %input-style;

  color: red;
}

트랜스파일링 결과는 아래와 같다.

.input-black, .input-red {
  font-size: 14px;
}

.input-black {
  color: black;
}

.input-red {
  color: red;
}

3. 조건과 반복

Sass는 프로그래밍 언어와 유사하게 제어문을 사용할 수 있는 기능을 제공한다.

3.1 if()

built-in if() 함수는 주어진 조건을 판단하여 결과를 리턴한다. Javascript의 삼항연산자와 유사하게 동작한다.

if(condition, if_true, if_false)

condition이 true이면 if_true를, false이면 if_false를 반환한다.

$type: ocean;

p {
  color: if($type == ocean, blue, black); // color: blue;
}

3.2 @if…@else

@if…@else를 사용하면 조건 분기가 가능하다.

$type: monster;

p {
  @if $type == ocean {
    color: blue;
  } @else if $type == matador {
    color: red;
  } @else if $type == monster {
    color: green;
  } @else {
    color: black;
  }
}

트랜스파일링 결과는 아래와 같다.

p {
  color: green;
}

3.3 @for

@for으로 반복문을 사용할 수 있다.

@for $i from 1 through 3 {
  .item-#{$i} { width: 2em * $i; }
}

트랜스파일링 결과는 아래와 같다.

.item-1 {
  width: 2em;
}
.item-2 {
  width: 4em;
}
.item-3 {
  width: 6em;
}

3.4 @each

@each와 list 또는 map의 요소에 대해 반복을 실시한다.

// List
@each $animal in puma, sea-slug, egret, salamander {

  .#{$animal}-icon {
    background-image: url('/images/#{$animal}.png');
  }
}

// Map
// $header: h1, $size: 2em
// $header: h2, $size: 1.5em
// $header: h3, $size: 1.2em
@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
  #{$header} {
    font-size: $size;
  }
}

트랜스파일링 결과는 아래와 같다.

.puma-icon {
  background-image: url("/images/puma.png");
}

.sea-slug-icon {
  background-image: url("/images/sea-slug.png");
}

.egret-icon {
  background-image: url("/images/egret.png");
}

.salamander-icon {
  background-image: url("/images/salamander.png");
}

h1 {
  font-size: 2em;
}

h2 {
  font-size: 1.5em;
}

h3 {
  font-size: 1.2em;
}

3.5 @while

@while으로 반복문을 사용할 수 있다.

$i: 6;
@while $i > 0 {
  .item-#{$i} { width: 2em * $i; }
  $i: $i - 2;
}

트랜스파일링 결과는 아래와 같다.

.item-6 {
  width: 12em;
}

.item-4 {
  width: 8em;
}

.item-2 {
  width: 4em;
}

4. Mixin

Mixin은 Sass의 매우 유용한 기능으로 중복 기술을 방지하기 위해 사용 빈도가 높은 마크업을 사전에 정의하여 필요할 때에 불러 사용하는 방법이다.

@extend와 유사하나 프로그래밍 언어의 함수와 같이 인수를 전달받을 수 있다는 차이가 있다.

사용법은 매우 간단하다. @mixin 선언하고 @include로 불러들인다.

// 지름이 50px인 원
@mixin circle {
  width: 50px;
  height: 50px;
  border-radius: 50%;
}

// 지름이 50px인 원을 위한 mixin을 include한 후 배경을 추가 지정
.box {
  @include circle;

  background: #f00;
}

트랜스파일링 결과는 아래와 같다. 배경이 red이고 지름이 50px인 원을 나타낸다.

.box {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: #f00;
}

@extend와 차이가 없어 보이나 Mixin은 함수와 같이 매개 변수를 사용할 수 있다.

@mixin circle($size) {
  width: $size;
  height: $size * 2;
  border-radius: 50%;
}

.box {
  @include circle(100px);

  background: #f00;
}

트랜스파일링 결과는 아래와 같다.

.box {
  width: 100px;
  height: 200px;
  border-radius: 50%;
  background: #f00;
}

매개 변수의 초기값을 설정할 수도 있다.

@mixin circle($size: 10px) {
  width: $size;
  height: $size * 2;
  border-radius: 50%;
}

.box {
  // 인수가 전달되지 않으면 초기값을 사용한다.
  @include circle();
  background: #f00;
}

트랜스파일링 결과는 아래와 같다.

.box {
  width: 10px;
  height: 20px;
  border-radius: 50%;
  background: #f00;
}

Mixin을 사용한 유용한 예제를 살펴보자.

vendor prefix

@mixin vendorPrefix($property, $value) {
  @each $prefix in -webkit-, -moz-, -ms-, -o-, '' {
    #{$prefix}#{$property}: $value;
  }
}

.border_radius {
  @include vendorPrefix(transition, 0.5s);
}
.border_radius {
  -webkit-transition: 0.5s;
  -moz-transition: 0.5s;
  -ms-transition: 0.5s;
  -o-transition: 0.5s;
  transition: 0.5s;
}

opacity

@mixin opacity($opacity) {
  opacity: $opacity; /* All modern browsers */
  $opacityIE: $opacity * 100;
  filter: alpha(opacity=$opacityIE); /* For IE5~IE9 */
}

.box {
  @include opacity(0.5);
}
.box {
  opacity: 0.5;
  /* All modern browsers */
  filter: alpha(opacity=50);
  /* For IE5~IE9 */
}

position

@mixin position($position, $top: null, $right: null, $bottom: null, $left: null) {
  position: $position;
  top: $top;
  right: $right;
  bottom: $bottom;
  left: $left;
}

.box {
  @include position(absolute, $top: 10px, $left: 50%);
}
.box {
  position: absolute;
  top: 10px;
  left: 50%;
}

이와 같이 Mixin을 작성하여 사용할 수도 있으나 Sass Framework/Library를 사용하는 것은 매우 바람직한 방법이다.

5. Function

Function은 mixin과 유사하나 반환값에 차이가 있다.

  • mixin : style markup을 반환

  • function : @return directive를 통하여 값을 반환

$grid-width: 40px;
$gutter-width: 10px;

@function grid-width($n) {
  @return $n * $grid-width + ($n - 1) * $gutter-width;
}

#sidebar { width: grid-width(5); }  // width: 240px;

6. Comment

CSS는 멀티 라인 주석 /* */만을 지원하지만 Sass는 /* */와 // 모두 사용할 수 있다.

한 줄 주석 //은 트랜스파일링 후 CSS에서 사라지고, 멀티 라인 주석은 CSS에 나타난다.

Reference

Back to top
Close