99

최근 30분간 동시 방문자 수를 표시합니다. (~)

최고 동시 방문자 수 -
어제: 0명 / 오늘: 0명

SCSS/Sass 완벽 가이드

SCSS/Sass 완벽 가이드

# 개요

이 글은 Dart Sass 1.104 버전을 기준으로 작성되었습니다.
설명은 SCSS 기준이며, 차이점이 있는 경우 별도로 Sass 설명을 추가합니다.

CSS는 상대적으로 배우기 쉽고 재미있습니다.
웹 개발 초심자에게는 이만큼 접근하기 좋은 게 없죠.

CSS는 분명 쉽고 재밌지만, 작업이 고도화될수록 불편함도 같이 커집니다.
불필요한 선택자(Selector)의 과용과 연산 기능의 한계, 구문(Statement)의 부재 등 프로젝트의 규모가 커질수록 아쉬움도 같이 커지죠.
하지만 웹에서는 표준 CSS만 동작할 수 있기 때문에 다른 선택권이 없습니다.

그렇다면 우리는 앞으로 계속 CSS만 사용해야 할까요?

# CSS 전처리기

HTML, CSS를 다루는 분이라면 한 번은 들어봤을 Sass, Less 등이 있습니다.
이 친구들은 CSS 전(예비)처리기 입니다.
보통 CSS Preprocessor 라고 부릅니다.

CSS가 동작하기 전에 사용하는 기능으로,
웹에서는 분명 CSS가 동작하지만 우리는 CSS의 불편함을 이런 확장 기능으로 상쇄할 수 있습니다.

사스는 기초 언어에 힘과 우아함을 더해주는 CSS의 확장이다.

표준 CSS도 그동안 많이 발전했습니다.
사용자 정의 속성(--color: red)으로 값을 변수처럼 사용할 수 있고, 선택자의 중첩(Nesting)도 모든 최신 브라우저에서 동작합니다.
그럼에도 전처리기를 계속 사용하는 이유는 모듈 시스템과 Mixins, 반복문처럼 컴파일 시점에 CSS를 만들어 내는 기능이 표준 CSS에는 없고, 새로 추가되는 CSS 기능은 모든 브라우저에서 동작하기까지 시간이 걸리기 때문입니다.

# 동작 방식

위에서 언급한 것처럼 웹에서는 CSS만 동작합니다.
Sass, Less, Stylus 같은 전처리기(이하 '전처리기'로 표기)는 직접 동작시킬 수 없습니다.
그렇다면 어떻게 사용할 수 있을까요?

CSS는 불편하니 일단 배제하고 우선 전처리기로 작성(코딩)합니다.
전처리기는 CSS 문법과 굉장히 유사하지만 선택자의 중첩(Nesting)이나 조건문, 반복문, 다양한 단위(Unit)의 연산 등... 표준 CSS 보다 훨씬 많은 기능을 사용해서 편리하게 작성할 수 있습니다.
단, 웹에서는 직접 동작하지 않으니 이렇게 작성한 전처리기를 웹에서 동작 가능한 표준의 CSS로 컴파일(Compile)합니다.
전처리기로 작성하고 CSS로 컴파일해서 동작시키는 거죠.

# Sass와 SCSS

Sass(Syntactically Awesome Style Sheets)의 3버전에서 새롭게 등장한 SCSS는 CSS 구문과 완전히 호환되도록 새로운 구문을 도입해 만든 Sass의 모든 기능을 지원하는 CSS의 상위집합(Superset) 입니다.
즉, SCSS는 CSS와 거의 같은 문법으로 Sass 기능을 지원한다는 말입니다.

더 쉽고 간단한 차이는 {}(중괄호)와 ;(세미콜론)의 유무입니다.
아래의 예제를 비교해 보세요.

Sass:

SASS
1
2
3
4
5
6
7
8
.list width: 100px float: left li color: red background: url("./image.jpg") &:last-child margin-right: -10px

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
.list { width: 100px; float: left; li { color: red; background: url("./image.jpg"); &:last-child { margin-right: -10px; } } }

Sass는 선택자의 유효범위를 '들여쓰기'로 구분하고, SCSS는 {}로 범위를 구분합니다.
Sass 방식과 SCSS 방식 중 어떤 방식이 마음에 드세요?

거의 유일합니다만, 다른 차이도 있습니다.
아래는 Mixins('믹스인'은 재사용 가능한 기능을 만드는 방식을 의미합니다) 예제입니다.
Sass는 단축 구문으로 사용합니다.

Sass:

SASS
1
2
3
4
5
6
7
=flex-center display: flex justify-content: center align-items: center .box +flex-center

SCSS:

SCSS
1
2
3
4
5
6
7
@mixin flex-center { display: flex; justify-content: center; align-items: center; } .box { @include flex-center; }

Sass는 =+ 기호로 Mixins 기능을 사용했고,
SCSS는 @mixin@include로 기능을 사용했습니다.

단순한 몇 가지를 제외하면 거의 차이가 없지만 복잡한 문장이 될 경우 여러 환경에 따른 장단점이 있을 수 있습니다.
Sass는 좀 더 간결하고 작성하기 편리하며, {};를 사용하지 않아도 되니 코드가 훨씬 깔끔해집니다.
SCSS는 인라인 코드(한 줄 작성)를 작성할 수 있고, CSS와 유사한 문법을 가지기 때문에 코드 통합이 훨씬 쉽습니다.

이렇게 몇몇 장단점이 있기 때문에 회사나 팀에서 원하는 방식을 사용해야 하거나, 개인 취향에 따라서 선택할 수 있습니다.
단지 상황에 맞는, 원하는 방식으로 골라서 사용하면 됩니다.

보통의 경우 SCSS를 추천합니다.

# 컴파일 방법

Sass(SCSS)는 웹에서 직접 동작할 수 없습니다.
어디까지나 최종에는 표준 CSS로 동작해야 하며, 우리는 전처리기로 작성 후 CSS로 컴파일해야 합니다.

현재 관리되는 공식 구현체는 Dart Sass 하나입니다.
과거에 사용하던 Ruby Sass와 LibSass(node-sass)는 모두 지원이 종료되었으니, sasssass-embedded 패키지를 사용합니다.
sass는 Dart Sass를 자바스크립트로 변환한 패키지이고, sass-embedded는 네이티브로 컴파일된 Dart Sass를 별도의 프로세스로 실행하는 패키지입니다.
문법은 같지만 sass-embedded의 컴파일 속도가 더 빠릅니다.

# Sass Playground

간단한 Sass 코드는 컴파일러를 설치하는 것이 부담될 수 있습니다.
그럴 경우 공식 문서가 제공하는 Sass Playground를 사용할 수 있습니다.

페이지에 접속한 후 바로 Sass나 SCSS 문법으로 작성하면 CSS로 실시간 변환됩니다.
문법을 확인하거나 컴파일 결과를 비교할 때 편리하니 학습에 도움이 될 것입니다.

# Sass CLI

sass 패키지를 설치하면 명령으로 직접 컴파일할 수 있습니다.

BASH
1
npm i -D sass
설치

하나의 파일을 컴파일하거나, 디렉토리 전체를 컴파일할 수 있습니다.

BASH
1
2
3
4
5
## `main.scss` 파일을 `main.css` 파일로 컴파일 npx sass main.scss main.css ## `scss` 디렉토리의 파일들을 `css` 디렉토리로 컴파일 npx sass scss:css

--watch 옵션을 추가하면 파일이 변경될 때마다 다시 컴파일합니다.

BASH
1
npx sass --watch scss:css

# Vite

Vite는 별도의 구성 없이 Sass(SCSS)를 지원합니다.
컴파일러 패키지만 설치하면 바로 사용할 수 있습니다.

BASH
1
npm i -D sass-embedded

Vite는 sass-embedded가 설치되어 있으면 그것을 우선 사용하고, 없으면 sass를 사용합니다.

# Webpack

sass-loader와 컴파일러 패키지를 함께 설치합니다.

BASH
1
npm i -D style-loader css-loader sass-loader sass-embedded
/webpack.config.js
JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = { module: { rules: [ { test: /\.s?[ac]ss$/i, use: [ 'style-loader', 'css-loader', 'sass-loader' ] } ] } }

# Parcel

Parcel은 HTML에 <link>로 SCSS/Sass 파일만 연결하면 됩니다.
다른 설정은 필요하지 않습니다.

BASH
1
npm i -D parcel sass
/index.html
HTML
1
<link rel="stylesheet" href="./src/main.scss">
BASH
1
npx parcel index.html

# 주석

CSS 주석(Comment)은 /* ... */ 입니다.
Sass(SCSS)는 JavaScript처럼 두 가지 스타일의 주석을 사용합니다.

SCSS
1
2
// 컴파일되지 않는 주석 /* 컴파일되는 주석 */

Sass의 경우 컴파일되는 여러 줄 주석을 사용할 때 각 줄 앞에 *을 붙여야 하고, 중요한 것은 *의 라인을 맞춰줘야 합니다.

Sass:

SASS
1
2
3
4
5
6
7
8
/* 컴파일되는 * 여러 줄 * 주석 */ // Error /* 컴파일되는 * 여러 줄 * 주석 */

SCSS는 각 줄에 *이 없어도 문제되지 않습니다.
따라서 기존 CSS와 호환이 쉽습니다.

SCSS:

SCSS
1
2
3
4
5
/* 컴파일되는 여러 줄 주석 */

# 데이터 종류

데이터 설명 예시
Numbers 숫자 1, .82, 20px, 2em
Strings 문자 bold, relative, "/images/a.png", "dotum"
Colors 색상 표현 red, blue, #FFFF00, rgba(255, 0, 0, .5)
Booleans 논리 true, false
Nulls 아무것도 없음 null
Lists 공백이나 ,로 구분된 값의 목록 (apple, orange, banana), apple orange
Maps Lists와 유사하나 값이 Key: Value 형태 (apple: a, orange: o, banana: b)

# 특이사항

Sass에서 사용하는 데이터 종류들의 몇 가지 특이사항을 소개합니다.

  • Numbers: 숫자에 단위가 있거나 없습니다.
  • Strings: 문자에 따옴표가 있거나 없습니다.
  • Nulls: 속성값으로 null이 사용되면 컴파일하지 않습니다.
  • Lists: ()를 붙이거나 붙이지 않습니다.
  • Maps: ()를 꼭 붙여야 합니다.

Colors는 rgbhsl 외에 CSS Color 4가 정의한 oklch, lab, display-p3 같은 색상 공간(Color Space)도 지원합니다.
색상 공간마다 채널(Channel)의 이름과 범위가 다르기 때문에, 색상 값을 계산할 때는 sass:color 모듈의 함수를 사용합니다.

# 중첩

Sass는 중첩(Nesting) 기능을 사용할 수 있습니다.
상위 선택자의 반복을 피하고 좀 더 편리하게 복잡한 구조를 작성할 수 있습니다.

중첩은 표준 CSS에도 추가되어 모든 최신 브라우저에서 동작합니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
.section { width: 100%; .list { padding: 20px; li { float: left; } } }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
.section { width: 100%; } .section .list { padding: 20px; } .section .list li { float: left; }

# Ampersand(&)

중첩 안에서 & 키워드는 상위(부모) 선택자를 참조하여 치환합니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.btn { position: absolute; &.active { color: red; } } .list { li { &:last-child { margin-right: 0; } } }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
.btn { position: absolute; } .btn.active { color: red; } .list li:last-child { margin-right: 0; }

& 키워드가 참조한 상위 선택자로 치환되는 것이기 때문에 다음과 같이 응용할 수도 있습니다.

SCSS:

SCSS
1
2
3
4
5
.fs { &-small { font-size: 12px; } &-medium { font-size: 14px; } &-large { font-size: 16px; } }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
.fs-small { font-size: 12px; } .fs-medium { font-size: 14px; } .fs-large { font-size: 16px; }

# @at-root

중첩에서 벗어나고 싶을 때 @at-root 키워드를 사용합니다.
중첩 안에서 생성하되 중첩 밖에서 사용해야 경우에 유용합니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
.list { $w: 100px; $h: 50px; li { width: $w; height: $h; } @at-root .box { width: $w; height: $h; } }

Compiled to:

CSS
1
2
3
4
5
6
7
8
.list li { width: 100px; height: 50px; } .box { width: 100px; height: 50px; }

아래 예제 처럼 .list 안에 있는 특정 변수를 범위 밖에서 사용할 수 없기 때문에, 위 예제 처럼 @at-root 키워드를 사용해야 합니다.(변수는 아래에서 설명합니다)

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.list { $w: 100px; $h: 50px; li { width: $w; height: $h; } } // Error .box { width: $w; height: $h; }

# 중첩된 속성

font-, margin- 등과 같이 동일한 네임 스페이스를 가지는 속성들을 다음과 같이 사용할 수 있습니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.box { font: { weight: bold; size: 10px; family: sans-serif; }; margin: { top: 10px; left: 20px; }; padding: { bottom: 40px; right: 30px; }; }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
.box { font-weight: bold; font-size: 10px; font-family: sans-serif; margin-top: 10px; margin-left: 20px; padding-bottom: 40px; padding-right: 30px; }

# 변수

반복적으로 사용되는 값을 변수로 지정할 수 있습니다.
변수 이름 앞에는 항상 $를 붙입니다.

SCSS
1
$변수이름: 속성값;

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
$color-primary: #e96900; $url-images: "/assets/images/"; $w: 200px; .box { width: $w; margin-left: $w; background: $color-primary url($url-images + "bg.jpg"); }

Compiled to:

CSS
1
2
3
4
5
.box { width: 200px; margin-left: 200px; background: #e96900 url("/assets/images/bg.jpg"); }

# 변수 유효범위

변수는 사용 가능한 유효범위(Scope)가 있습니다.
선언된 블록({}) 내에서만 유효범위를 가집니다.

변수 $color.box1의 블록 안에서 설정되었기 때문에, 블록 밖의 .box2에서는 사용할 수 없습니다.

SCSS
1
2
3
4
5
6
7
8
.box1 { $color: #111; background: $color; } // Error .box2 { background: $color; }

# 변수 재 할당

다음과 같이 변수에 변수를 다시 할당(Reassignment)할 수 있습니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
$red: #FF0000; $blue: #0000FF; $color-primary: $blue; $color-danger: $red; .box { color: $color-primary; background: $color-danger; }

Compiled to:

CSS
1
2
3
4
.box { color: #0000FF; background: #FF0000; }

# !global

!global 플래그를 사용하면 블록 안에서 전역(Global) 변수의 값을 바꿀 수 있습니다.
!global이 없으면 블록 안에서 할당한 변수는 같은 이름의 전역 변수와는 별개인 지역 변수가 됩니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
$color: #000; .box1 { $color: #111 !global; background: $color; } .box2 { background: $color; } .box3 { $color: #222; background: $color; }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
.box1 { background: #111; } .box2 { background: #111; } .box3 { background: #222; }

.box1에서 !global로 전역 변수 $color의 값을 바꿨기 때문에, 뒤에 오는 .box2도 바뀐 값을 사용합니다.
반면 .box3$color는 지역 변수이므로 전역 변수의 값은 그대로 #111입니다.

최상위에 선언하지 않은 변수를 !global로 새로 만드는 방식은 지원이 중단되었고, Dart Sass 2.0.0부터는 동작하지 않습니다.
전역 변수는 최상위에 $color: null처럼 먼저 선언한 뒤에 !global로 값을 할당해야 합니다.

# !default

!default 플래그는 할당되지 않은 변수의 초깃값을 설정합니다.
즉, 이미 값이 할당된 변수가 있다면 기존 값을 그대로 사용하고, 없을 때만 !default로 설정한 값을 사용합니다.

SCSS:

SCSS
1
2
3
4
5
6
$color-primary: red; .box { $color-primary: blue !default; background: $color-primary; }

Compiled to:

CSS
1
2
3
.box { background: red; }

!default는 주로 라이브러리나 여러 곳에서 함께 사용하는 모듈에서 사용합니다.
모듈 안의 변수에 !default를 붙이면, 그 모듈을 가져오는 쪽에서 원하는 값으로 바꿀 수 있습니다.
반대로 !default가 없는 변수는 모듈 밖에서 바꿀 수 없습니다.

/scss/_config.scss
SCSS
1
2
$color-active: red !default; $size-base: 16px !default;

다음은 Bootstrap 코드의 일부입니다.
모든 변수에 !default가 붙어 있어서, Bootstrap을 사용하는 쪽에서 원하는 값만 바꿀 수 있습니다.

/scss/_variables.scss
SCSS
1
2
3
4
5
6
$white: #fff !default; $gray-100: #f8f9fa !default; $blue: #0d6efd !default; $primary: $blue !default; $font-size-base: 1rem !default; $border-radius: 0.375rem !default;

모듈을 가져오면서 변수의 값을 바꾸는 방법은 모듈 설정에서 살펴봅니다.

# #{}

#{}를 이용해서 코드의 어디든지 변수 값을 넣을 수 있습니다.
이를 문자 보간(Interpolation)이라고 합니다.

SCSS
1
2
3
4
@use "sass:string"; $family: string.unquote("Noto+Sans+KR"); @import url("https://fonts.googleapis.com/css2?family=#{$family}");
CSS
1
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR");

sass:string 모듈의 string.unquote() 함수는 문자에서 따옴표를 제거합니다.
내장 함수는 내장 모듈에서 자세히 살펴봅니다.

# 모듈

프로젝트 규모가 커지면 파일들을 headerside-menu 같이 각 기능과 부분으로 나눠 유지보수가 쉽도록 관리하게 됩니다.
이렇게 나눈 파일은 @use로 가져와서 하나의 CSS로 합칩니다.

과거에는 @import로 파일을 가져왔지만, Sass의 @import는 Dart Sass 1.80.0부터 지원이 중단(Deprecated)되었고 Dart Sass 3.0.0에서 제거될 예정입니다.
새로 작성하는 코드에서는 @use@forward를 사용하세요.

# 파일 분할

파일을 나누면 파일이 점점 많아지는데, 모든 파일이 컴파일 시 각각의 ~.css 파일로 나눠서 저장된다면 관리나 성능 차원에서 문제가 될 수 있겠죠.
그래서 Sass는 파일 분할(Partials) 기능을 지원합니다.
파일 이름 앞에 _를 붙이면(_header.scss와 같이) 그 파일은 별도의 ~.css 파일로 컴파일하지 않고, 다른 파일에서 가져와서 사용합니다.

예를 들어보겠습니다.
다음과 같이 scss/ 안에 3개의 Sass 파일이 있습니다.

BASH
1
2
3
4
5
6
7
Sass-App # ... ├─scss ├─_header.scss ├─_side-menu.scss └─main.scss # ...

main.scss에서 나머지 두 파일을 가져옵니다.
가져올 때 파일 이름의 _와 확장자는 생략합니다.

/scss/main.scss
SCSS
1
2
@use "header"; @use "side-menu";

그리고 이 파일들을 css/ 디렉토리로 컴파일합니다.

BASH
1
npx sass scss:css

컴파일 후 확인하면 아래처럼 _가 붙은 파일들은 별도의 파일로 컴파일되지 않고 main.css 하나로 합쳐집니다.

BASH
1
2
3
4
5
6
7
8
9
Sass-App # ... ├─css └─main.css # main + header + side-menu ├─scss ├─_header.scss ├─_side-menu.scss └─main.scss # ...

_를 붙이지 않으면 header.cssside-menu.css도 함께 만들어집니다.
Webpack이나 Vite 같은 빌드 도구에서는 진입점 파일 하나만 컴파일하기 때문에 이런 문제가 없지만, 되도록 _를 사용할 것을 권장합니다.

# @use

@use로 가져온 모듈의 변수와 Mixins, 함수는 파일 이름을 네임스페이스(Namespace)로 사용해서 접근합니다.
@import처럼 모든 이름이 전역으로 섞이지 않기 때문에, 어떤 파일에서 온 이름인지 코드만 보고 알 수 있습니다.

/scss/_config.scss
SCSS
1
2
3
4
5
6
7
8
$primary: royalblue; $radius: 4px; @mixin flex-center { display: flex; justify-content: center; align-items: center; }
/scss/main.scss
SCSS
1
2
3
4
5
6
7
@use "config"; .btn { background: config.$primary; border-radius: config.$radius; @include config.flex-center; }

as로 네임스페이스의 이름을 바꿀 수 있습니다.

SCSS
1
2
3
4
5
@use "config" as c; .btn { background: c.$primary; }

as *를 사용하면 네임스페이스 없이 사용할 수 있습니다.
편리하지만 이름이 충돌할 수 있으니 되도록 사용하지 않는 것이 좋습니다.

SCSS
1
2
3
4
5
@use "config" as *; .btn { background: $primary; }

같은 파일을 여러 곳에서 @use로 가져와도 실제로는 한 번만 컴파일하기 때문에, 중복된 CSS가 만들어지지 않습니다.

@use는 파일의 가장 위에 작성해야 합니다.
@charset과 주석을 제외한 다른 규칙보다 앞에 있어야 하며, 선택자 블록 안에서는 사용할 수 없습니다.
단, 모듈을 설정할 때 전달할 변수는 @use보다 앞에 선언할 수 있습니다.

이름 앞에 _-를 붙이면 그 변수와 Mixin, 함수는 해당 파일 안에서만 사용할 수 있습니다.
모듈 밖으로 공개하지 않을 이름을 구분할 때 사용합니다.

/scss/_config.scss
SCSS
1
2
$-inner-gap: 4px; // `config.$-inner-gap`으로 접근할 수 없음 $gap: $-inner-gap * 2;

# 모듈 설정

!default 플래그가 붙은 변수는 with로 값을 바꿔서 가져올 수 있습니다.
!default가 없는 변수는 with로 설정할 수 없습니다.

/scss/_config.scss
SCSS
1
2
$primary: royalblue !default; $radius: 4px !default;
/scss/main.scss
SCSS
1
2
3
4
5
6
7
8
9
@use "config" with ( $primary: #e96900, $radius: 8px ); .btn { background: config.$primary; // #e96900 border-radius: config.$radius; // 8px }

하나의 모듈은 한 번만 설정할 수 있습니다.
다른 파일에서 이미 @use로 가져온 모듈을 다시 with로 설정하면 에러가 발생합니다.

# @forward

@forward는 다른 파일의 변수와 Mixins, 함수를 그대로 다시 내보냅니다.
여러 조각 파일을 하나의 진입점으로 묶을 때 사용합니다.

/scss/_index.scss
SCSS
1
2
3
@forward "config"; @forward "button"; @forward "header";

디렉토리 이름으로 @use하면 그 안의 _index.scss를 가져옵니다.
scss/ 안의 파일이 몇 개든 사용하는 쪽에서는 하나의 모듈로 다룰 수 있습니다.

/main.scss
SCSS
1
2
3
4
5
6
@use "scss" as ui; .btn { background: ui.$primary; @include ui.flex-center; }

showhide로 내보낼 대상을 고를 수 있고, as로 접두어를 붙일 수도 있습니다.

SCSS
1
2
3
@forward "config" show $primary, flex-center; @forward "button" hide $btn-inner-gap; @forward "config" as config-*;

as config-*로 접두어를 붙이면 변수는 ui.$config-primary, Mixin과 함수는 ui.config-flex-center처럼 사용합니다.

# @import

@import는 가져온 파일의 모든 이름을 전역으로 합칩니다.
이름이 충돌하기 쉽고, 어떤 파일에서 온 이름인지 알기 어려우며, 같은 파일을 여러 번 가져오면 CSS도 그만큼 중복됩니다.
이런 문제 때문에 지원이 중단되었습니다.

SCSS
1
2
// 지원이 중단된 방식 @import "header", "side-menu";

단, 아래의 경우는 Sass의 @import가 아니라 CSS @import 규칙으로 컴파일되며 계속 사용할 수 있습니다.

  • 파일 확장자가 .css일 때
  • 파일 이름이 http://https://로 시작하는 경우
  • url()이 붙었을 경우
  • 미디어쿼리가 있는 경우
SCSS
1
2
3
4
@import "hello.css"; @import "https://hello.com/hello"; @import url(hello); @import "hello" screen;

CSS 파일의 내용을 Sass 코드 안으로 가져오려면 @use에 확장자를 함께 작성합니다.

SCSS
1
@use "hello.css";

# 연산

Sass는 기본적인 연산(Operations) 기능을 지원합니다.
레이아웃 작업시 상황에 맞게 크기를 계산을 하거나 정해진 값을 나눠서 작성할 경우 유용합니다.
다음은 Sass에서 사용 가능한 연산자 종류 입니다.

산술 연산자:

종류 설명 주의사항
+ 더하기
- 빼기
* 곱하기 하나 이상의 값이 반드시 숫자(Number)
% 나머지

나누기는 산술 연산자가 아니라 sass:math 모듈의 math.div() 함수나 calc()를 사용합니다.
아래 숫자 연산에서 자세히 살펴봅니다.

비교 연산자:

종류 설명
== 동등
!= 부등
< 대소 / 보다 작은
> 대소 / 보다 큰
<= 대소 및 동등 / 보다 작거나 같은
>= 대소 및 동등 / 보다 크거나 같은

논리(불린, Boolean) 연산자:

종류 설명
and 그리고
or 또는
not 부정

# 숫자

단위가 같거나 서로 변환할 수 있는 단위(pxin, em끼리 등)는 바로 연산할 수 있습니다.
하지만 %px처럼 컴파일 시점에 변환할 수 없는 단위끼리는 연산할 수 없으니, 이런 경우 CSS calc()를 사용합니다.

SCSS
1
2
width: 50% - 20px; // Error: 50% and 20px have incompatible units. width: calc(50% - 20px); // 브라우저에서 계산

CSS는 속성값의 숫자를 분리하는 방법으로 /를 허용합니다.
예를 들어, font: 16px / 22px serif; 같은 경우 font-size: 16pxline-height: 22px의 속성값 분리를 위해서 /를 사용합니다.
이렇게 의미가 겹치기 때문에 /는 나누기 연산자에서 제외되었고, 나누기는 math.div() 함수나 calc()로 처리합니다.
$width / 2처럼 /로 나누는 기존 코드는 아직 동작하지만 컴파일할 때마다 경고가 출력되고, Dart Sass 2.0.0에서 제거될 예정입니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
@use "sass:math"; div { width: 20px + 20px; // 더하기 height: 40px - 10px; // 빼기 font-size: 10px * 2; // 곱하기 margin: math.div(30px, 2); // 나누기 padding: calc(30px / 2); // 나누기 }

Compiled to:

CSS
1
2
3
4
5
6
7
div { width: 40px; height: 30px; font-size: 20px; margin: 15px; padding: 15px; }

math.div()는 컴파일 시점에 값을 계산합니다.
calc()는 계산할 수 있으면 컴파일 시점에 계산하고, 그렇지 않으면 CSS calc()를 그대로 출력합니다.
그래서 CSS 사용자 정의 속성처럼 컴파일 시점에 값을 알 수 없는 대상은 calc()로 계산해야 합니다.

SCSS
1
2
3
div { width: calc(var(--size) / 2); }
CSS
1
2
3
div { width: calc(var(--size) / 2); }

# 문자

문자 연산에는 +가 사용됩니다.
문자 연산의 결과는 첫 번째 피연산자를 기준으로 합니다.
첫 번째 피연산자에 따옴표가 붙어있다면 연산 결과를 따옴표로 묶습니다.
반대로 첫 번째 피연산자에 따옴표가 붙어있지 않다면 연산 결과도 따옴표를 처리하지 않습니다.

SCSS:

SCSS
1
2
3
4
div::after { content: "Hello " + World; flex-flow: row + "-reverse" + " " + wrap }

Compiled to:

CSS
1
2
3
4
div::after { content: "Hello World"; flex-flow: row-reverse wrap; }

# 색상

색상 값은 산술 연산자로 계산할 수 없습니다.
채널 단위의 계산으로는 원하는 색을 만들기 어렵고 결과를 예상하기도 힘들기 때문에, 색상 연산은 제거되었습니다.
대신 sass:color 모듈의 함수를 사용합니다.

SCSS
1
2
3
4
div { // Error: Undefined operation "#123456 + #345678". color: #123456 + #345678; }

color.adjust()는 채널 값을 더하거나 빼고, color.scale()은 채널이 가진 남은 범위를 기준으로 비율만큼 조절하며, color.mix()는 두 색을 섞습니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
@use "sass:color"; $color: rgba(10, 20, 30, 0.5); div { color: color.adjust($color, $alpha: 0.3); // 30% 더 불투명하게 / 0.5 + 0.3 background-color: color.adjust($color, $alpha: -0.2); // 20% 더 투명하게 / 0.5 - 0.2 border-color: color.scale(#336699, $lightness: 50%); // 남은 밝기 범위의 50%만큼 더 밝게 outline-color: color.mix(red, blue, 30%); // 두 색을 3:7로 섞기 }

Compiled to:

CSS
1
2
3
4
5
6
div { color: rgba(10, 20, 30, 0.8); background-color: rgba(10, 20, 30, 0.3); border-color: rgb(55%, 70%, 85%); outline-color: rgb(30%, 0%, 70%); }

lighten(), darken(), opacify(), transparentize() 같은 기존 색상 함수는 Dart Sass 1.79.0부터 지원이 중단되었고, Dart Sass 2.0.0에서 제거될 예정입니다.
lighten($color, 10%)color.adjust($color, $lightness: 10%)로, transparentize($color, 0.2)color.adjust($color, $alpha: -0.2)로 바꿔서 작성합니다.

# 논리

Sass의 @if 조건문에서 사용되는 논리(Boolean) 연산에는 '그리고', '또는', '부정'이 있습니다.
자바스크립트 문법에 익숙하다면 &&, ||, !와 같은 기능으로 생각하면 됩니다.

간단한 예제를 확인하고, 더 자세한 내용은 조건문에서 살펴보겠습니다.

SCSS:

SCSS
1
2
3
4
5
6
$width: 90px; div { @if not ($width > 100px) { height: 300px; } }

Compiled to:

CSS
1
2
3
div { height: 300px; }

# Mixins

Sass Mixins는 스타일 시트 전체에서 재사용할 CSS 선언 그룹을 정의하는 기능입니다.
약간의 Mixin(믹스인)으로 다양한 스타일을 만들어낼 수 있습니다.

우선, Mixin은 두 가지만 기억하면 됩니다.
선언하기(@mixin)와 포함하기(@include) 입니다.
만들어서(선언), 사용(포함)하는 거죠!

# @mixin

기본적인 Mixin 선언은 아주 간단합니다.
@mixin 지시어를 이용하여 스타일을 정의합니다.

SCSS:

SCSS
1
2
3
@mixin 믹스인이름 { 스타일; }

Sass:

SASS
1
2
=믹스인이름 스타일

SCSS:

SCSS
1
2
3
4
5
6
@mixin large-text { font-size: 22px; font-weight: bold; font-family: sans-serif; color: orange; }

Mixin은 선택자를 포함 가능하고 상위(부모) 요소 참조(& 같은)도 할 수 있습니다.

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@mixin large-text { font: { size: 22px; weight: bold; family: sans-serif; } color: orange; &::after { content: "!!"; } span.icon { background: url("/images/icon.png"); } }

# @include

선언된 Mixin을 사용(포함)하기 위해서는 @include가 필요합니다.
위에서 선언한 Mixin을 사용해 보겠습니다.

SCSS:

SCSS
1
@include 믹스인이름;

Sass:

SASS
1
+믹스인이름

SCSS:

SCSS
1
2
3
4
5
6
h1 { @include large-text; } div { @include large-text; }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
h1 { font-size: 22px; font-weight: bold; font-family: sans-serif; color: orange; } h1::after { content: "!!"; } h1 span.icon { background: url("/images/icon.png"); } div { font-size: 22px; font-weight: bold; font-family: sans-serif; color: orange; } div::after { content: "!!"; } div span.icon { background: url("/images/icon.png"); }

# 인수

Mixin은 함수(Functions)처럼 인수(Arguments)를 가질 수 있습니다.
하나의 Mixin으로 다양한 결과를 만들 수 있습니다.

SCSS:

SCSS
1
2
3
4
@mixin 믹스인이름($매개변수) { 스타일; } @include 믹스인이름(인수);

Sass:

SASS
1
2
3
4
=믹스인이름($매개변수) 스타일 +믹스인이름(인수)

매개변수(Parameters)란 변수의 한 종류로, 제공되는 여러 데이터 중 하나를 가리키기 위해 사용된다.
제공되는 여러 데이터들을 전달인수(Arguments) 라고 부른다.

SCSS:

SCSS
1
2
3
4
5
6
@mixin dash-line($width, $color) { border: $width dashed $color; } .box1 { @include dash-line(1px, red); } .box2 { @include dash-line(4px, blue); }

Compiled to:

CSS
1
2
3
4
5
6
.box1 { border: 1px dashed red; } .box2 { border: 4px dashed blue; }

# 인수의 기본값 설정

인수(argument)는 기본값(default value)을 가질 수 있습니다.
@include 포함 단계에서 별도의 인수가 전달되지 않으면 기본값이 사용됩니다.

SCSS
1
2
3
@mixin 믹스인이름($매개변수: 기본값) { 스타일; }

SCSS:

SCSS
1
2
3
4
5
6
@mixin dash-line($width: 1px, $color: black) { border: $width dashed $color; } .box1 { @include dash-line; } .box2 { @include dash-line(4px); }

Compiled to:

CSS
1
2
3
4
5
6
.box1 { border: 1px dashed black; } .box2 { border: 4px dashed black; }

# 키워드 인수

SCSS
1
2
3
4
5
@mixin 믹스인이름($매개변수A: 기본값, $매개변수B: 기본값) { 스타일; } @include 믹스인이름($매개변수B: 인수);

Mixin에 전달할 인수를 입력할 때 명시적으로 키워드(변수)를 입력하여 작성할 수 있고, 이를 키워드 인수(Keyword Arguments)라고 합니다.
별도의 인수 입력 순서를 필요로 하지 않아 편리하게 작성할 수 있습니다.
단, 작성하지 않은 인수가 적용될 수 있도록 기본값을 설정해 주는 것이 좋습니다.

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@mixin position( $p: absolute, $t: null, $b: null, $l: null, $r: null ) { position: $p; top: $t; bottom: $b; left: $l; right: $r; } .absolute { // 키워드 인수로 설정할 값만 전달 @include position($b: 10px, $r: 20px); } .fixed { // 인수가 많아짐에 따라 가독성을 확보하기 위해 줄바꿈 @include position( fixed, $t: 30px, $r: 40px ); }
CSS
1
2
3
4
5
6
7
8
9
10
.absolute { position: absolute; bottom: 10px; right: 20px; } .fixed { position: fixed; top: 30px; right: 40px; }

# 가변 인수

때때로 입력할 인수의 개수가 불확실한 경우가 있습니다.
그럴 경우 가변 인수(Variable Arguments)를 사용할 수 있습니다.
가변 인수는 매개변수 뒤에 ...을 붙여줍니다.

SCSS
1
2
3
4
5
@mixin 믹스인이름($매개변수...) { 스타일; } @include 믹스인이름(인수A, 인수B, 인수C);
SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 인수를 순서대로 하나씩 전달 받다가, 3번째 매개변수($bg-values)는 인수의 개수에 상관없이 받음 @mixin bg($width, $height, $bg-values...) { width: $width; height: $height; background: $bg-values; } div { // 위의 Mixin(bg) 설정에 맞게 인수를 순서대로 전달하다가 3번째 이후부터는 개수에 상관없이 전달 @include bg( 100px, 200px, url("/images/a.png") no-repeat 10px 20px, url("/images/b.png") no-repeat, url("/images/c.png") ); }
CSS
1
2
3
4
5
6
7
div { width: 100px; height: 200px; background: url("/images/a.png") no-repeat 10px 20px, url("/images/b.png") no-repeat, url("/images/c.png"); }

위에선 인수를 받는 매개변수에 ...을 사용하여 가변 인수를 활용했습니다.
이번엔 반대로 가변 인수를 전달할 값으로 사용해 보겠습니다.

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@mixin font( $style: normal, $weight: normal, $size: 16px, $family: sans-serif ) { font: { style: $style; weight: $weight; size: $size; family: $family; } } div { // 매개변수 순서와 개수에 맞게 전달 $font-values: italic, bold, 16px, sans-serif; @include font($font-values...); } span { // 필요한 값만 키워드 인수로 변수에 담아 전달 $font-values: (style: italic, size: 22px); @include font($font-values...); } a { // 필요한 값만 키워드 인수로 전달 @include font((weight: 900, family: monospace)...); }
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
div { font-style: italic; font-weight: bold; font-size: 16px; font-family: sans-serif; } span { font-style: italic; font-weight: normal; font-size: 22px; font-family: sans-serif; } a { font-style: normal; font-weight: 900; font-size: 16px; font-family: monospace; }

# @content

선언된 Mixin에 @content이 포함되어 있다면 해당 부분에 원하는 스타일 블록을 전달할 수 있습니다.
이 방식을 사용하여 기존 Mixin이 가지고 있는 기능에 선택자나 속성 등을 추가할 수 있습니다.

SCSS
1
2
3
4
5
6
7
8
9
@mixin 믹스인이름() { 스타일; @content; } @include 믹스인이름() { // 스타일 블록 스타일; }

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@mixin icon($url) { &::after { content: $url; @content; } } .icon1 { // icon Mixin의 기존 기능만 사용 @include icon("/images/icon.png"); } .icon2 { // icon Mixin에 스타일 블록을 추가하여 사용 @include icon("/images/icon.png") { position: absolute; }; }

Compiled to:

CSS
1
2
3
4
5
6
7
.icon1::after { content: "/images/icon.png"; } .icon2::after { content: "/images/icon.png"; position: absolute; }

Mixin에게 전달된 스타일 블록은 Mixin의 범위가 아니라 스타일 블록이 정의된 범위에서 평가됩니다.
즉, Mixin의 매개변수는 전달된 스타일 블록 안에서 사용되지 않고 전역 값으로 해석됩니다.
전역 변수(Global variables)와 지역 변수(Local variables)를 생각하면 좀 더 쉽습니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$color: red; @mixin colors($color: blue) { // Mixin의 범위 @content; background-color: $color; border-color: $color; } div { @include colors() { // 스타일 블록이 정의된 범위 color: $color; } }

Compiled to:

CSS
1
2
3
4
5
div { color: red; background-color: blue; border-color: blue; }

Mixin에서 전달하는 스타일 블록으로 인수를 넘길 수도 있습니다.
@content()로 인수를 전달하고, @include 뒤에 using을 작성해 매개변수로 전달받습니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
@mixin media($breakpoint) { @media (min-width: $breakpoint) { @content($breakpoint); } } .box { @include media(768px) using ($width) { max-width: $width; } }

Compiled to:

CSS
1
2
3
4
5
@media (min-width: 768px) { .box { max-width: 768px; } }

# 확장

특정 선택자가 다른 선택자의 모든 스타일을 가져야하는 경우가 종종 있습니다.
이럴 경우 선택자의 확장(Extend) 기능을 사용할 수 있습니다.
다음 예제를 봅시다.

SCSS
1
@extend 선택자;

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
.btn { padding: 10px; margin: 10px; background: blue; } .btn-danger { @extend .btn; background: red; }

Compiled to:

CSS
1
2
3
4
5
6
7
8
.btn, .btn-danger { padding: 10px; margin: 10px; background: blue; } .btn-danger { background: red; }

컴파일된 결과가 마음에 드시나요?
결과를 보면 ,로 구분하는 다중 선택자(Multiple Selector)가 만들어졌습니다.

사실 @extend는 다음과 같은 문제를 고려해야 합니다.

  • 내 현재 선택자(위 예제의 .btn-danger)가 어디에 첨부될 것인가?
  • 원치 않는 부작용이 초래될 수도 있는가?
  • 이 한 번의 확장으로 얼마나 큰 CSS가 생성되는가?

결과적으로 확장(Extend) 기능은 무해하거나 혹은 유익할 수도 있지만 그만큼 부작용을 가지고 있을 수 있습니다.
따라서 확장은 사용을 권장하지 않으며, 위에서 살펴본 Mixin을 대체 기능으로 사용하세요.

굳이 확장을 사용해야 한다면 일반 선택자 대신 자리 표시자 선택자(Placeholder Selector)를 사용합니다.
%로 시작하는 자리 표시자 선택자는 그 자체로는 CSS로 컴파일되지 않고, @extend로 확장한 곳에서만 출력됩니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
%btn-base { padding: 10px; margin: 10px; } .btn { @extend %btn-base; background: blue; } .btn-danger { @extend %btn-base; background: red; }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
.btn-danger, .btn { padding: 10px; margin: 10px; } .btn { background: blue; } .btn-danger { background: red; }

또한 @extend@media 같은 조건부 블록 밖에 있는 선택자를 확장할 수 없습니다.
미디어쿼리 안에서 스타일을 재사용해야 한다면 Mixin을 사용해야 합니다.

사용을 권장하지 않는 이유에 대해서 좀 더 자세한 정보를 원하면 Sass Guidelines Extend를 참고하세요.

# 함수

자신의 함수(Functions)를 정의하여 사용할 수 있습니다.
함수와 Mixins은 거의 유사하지만 반환되는 내용이 다릅니다.

Mixin은 위에서 살펴본 대로 지정한 스타일(Style)을 반환하는 반면,
함수는 보통 연산된(Computed) 특정 값을 @return 지시어를 통해 반환합니다.

SCSS
1
2
3
4
5
6
7
8
9
// Mixins @mixin 믹스인이름($매개변수) { 스타일; } // Functions @function 함수이름($매개변수) { @return }

사용하는 방법에도 차이가 있습니다.
Mixin은 @include 지시어를 사용하는 반면,
함수는 함수이름으로 바로 사용합니다.

SCSS
1
2
3
4
5
// Mixin @include 믹스인이름(인수); // Functions 함수이름(인수)

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@use "sass:math"; $max-width: 980px; @function columns($number: 1, $columns: 12) { @return math.div($max-width * $number, $columns); } .box_group { width: $max-width; .box1 { width: columns(); // 1 } .box2 { width: columns(8); } .box3 { width: columns(3); } }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.box_group { /* 총 너비 */ width: 980px; } .box_group .box1 { /* 총 너비의 약 8.3% */ width: 81.6666666667px; } .box_group .box2 { /* 총 너비의 약 66.7% */ width: 653.3333333333px; } .box_group .box3 { /* 총 너비의 25% */ width: 245px; }

위와 같이 함수는 @include 같은 별도의 지시어 없이 사용합니다.
내장 함수는 math.div()처럼 네임스페이스를 붙여서 호출하기 때문에 내가 정의한 함수와 이름이 충돌하지 않습니다.
다만 @use ... as *로 네임스페이스 없이 가져온 함수와는 이름이 충돌할 수 있습니다.
여러 파일에서 함께 사용하는 함수라면 columns()보다 ui-columns()처럼 구분되는 이름을 사용하는 것이 좋습니다.

# 조건과 반복

# if 함수

조건의 값(true, false)에 따라 두 개의 표현식 중 하나만 반환합니다.
조건부 삼항 연산자(conditional ternary operator)와 비슷합니다.

CSS에도 if() 함수가 추가되면서, Sass의 if()도 CSS와 같은 구문을 사용합니다.
sass() 안에 Sass 조건을 작성하면 컴파일 시점에 조건을 확인해서, 조건이 참이면 : 뒤의 표현식을, 거짓이면 else: 뒤의 표현식을 반환합니다.
sass(조건): 표현식; 쌍을 여러 개 작성하면 참인 첫 번째 조건의 표현식을 반환합니다.

SCSS
1
if(sass(조건): 표현식1; else: 표현식2)

SCSS:

SCSS
1
2
3
4
$width: 555px; div { width: if(sass($width > 300px): $width; else: null); }

Compiled to:

CSS
1
2
3
div { width: 555px; }

if($width > 300px, $width, null) 형태의 기존 구문은 Dart Sass 1.95.0부터 지원이 중단되었습니다.
새 구문도 Dart Sass 1.95.0부터 사용할 수 있으니, 그보다 낮은 버전을 사용하는 프로젝트라면 컴파일러를 먼저 올려야 합니다.

# @if

@if 지시어는 조건에 따른 분기 처리가 가능하며, if 문(if statements)과 유사합니다.
같이 사용할 수 있는 지시어는 @else, if가 있습니다.
추가 지시어를 사용하면 좀 더 복잡한 조건문을 작성할 수 있습니다.

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// @if @if (조건) { /* 조건이 참일 때 구문 */ } // @if @else @if (조건) { /* 조건이 참일 때 구문 */ } @else { /* 조건이 거짓일 때 구문 */ } // @if @else if @if (조건1) { /* 조건1이 참일 때 구문 */ } @else if (조건2) { /* 조건2가 참일 때 구문 */ } @else { /* 모두 거짓일 때 구문 */ }

조건에 ()는 생략이 가능하기 때문에, () 없이 작성하는 방법이 좀 더 편리할 수 있습니다.

SCSS
1
2
3
4
5
6
$bg: true; div { @if $bg { background: url("/images/a.jpg"); } }

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
$color: orange; div { @if $color == strawberry { color: #FE2E2E; } @else if $color == orange { color: #FE9A2E; } @else if $color == banana { color: #FFFF00; } @else { color: #2A1B0A; } }

Compiled to:

CSS
1
2
3
div { color: #FE9A2E; }

조건에는 논리 연산자 and, or, not을 사용할 수 있습니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
@function limitSize($size) { @if $size >= 0 and $size <= 200px { @return 200px; } @else { @return 800px; } } div { width: limitSize(180px); height: limitSize(340px); }

Compiled to:

CSS
1
2
3
4
div { width: 200px; height: 800px; }

다른 예제도 살펴봅시다!
sass:math 모듈의 math.is-unitless() 함수는 숫자에 단위가 없는지 여부를 반환합니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@use "sass:math"; @mixin pCenter($w, $h, $p: absolute) { @if $p == absolute or $p == fixed // or not $p == relative // or not $p == static { width: if(sass(math.is-unitless($w)): #{$w}px; else: $w); height: if(sass(math.is-unitless($h)): #{$h}px; else: $h); position: $p; top: 0; bottom: 0; left: 0; right: 0; margin: auto; } } .box1 { @include pCenter(10px, 20px); } .box2 { @include pCenter(50, 50, fixed); } .box3 { @include pCenter(100, 200, relative); }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.box1 { width: 10px; height: 20px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; } .box2 { width: 50px; height: 50px; position: fixed; top: 0; bottom: 0; left: 0; right: 0; margin: auto; }

# @for

@for는 스타일을 반복적으로 출력합니다.
for 문과 유사합니다.

@forthrough를 사용하는 형식과 to를 사용하는 형식으로 나뉩니다.
두 형식은 종료 조건이 해석되는 방식이 다릅니다.

SCSS
1
2
3
4
5
6
7
8
9
10
11
// through // 종료 만큼 반복 @for $변수 from 시작 through 종료 { // 반복 내용 } // to // 종료 직전까지 반복 @for $변수 from 시작 to 종료 { // 반복 내용 }

차이점을 이해하기 위해 다음 예제를 살펴봅시다.
변수는 관례상 $i를 사용합니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
// 1부터 3번 반복 @for $i from 1 through 3 { .through:nth-child(#{$i}) { width : 20px * $i } } // 1부터 3 직전까지만 반복(2번 반복) @for $i from 1 to 3 { .to:nth-child(#{$i}) { width : 20px * $i } }

Compiled to:

CSS
1
2
3
4
5
6
.through:nth-child(1) { width: 20px; } .through:nth-child(2) { width: 40px; } .through:nth-child(3) { width: 60px; } .to:nth-child(1) { width: 20px; } .to:nth-child(2) { width: 40px; }

to는 주어진 값 직전까지만 반복해야할 경우 유용할 수 있습니다.
그러나 :nth-child()에서 특히 유용하게 사용되는 @for는 일반적으로 through를 사용하길 권장합니다.

# @each

@each는 List와 Map 데이터를 반복할 때 사용합니다.
for in 문과 유사합니다.

SCSS
1
2
3
@each $변수 in 데이터 { // 반복 내용 }

List 데이터를 반복해 보겠습니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
// List Data $fruits: (apple, melon, banana, mango); .fruits { @each $fruit in $fruits { li.#{$fruit} { background: url("/images/#{$fruit}.png"); } } }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
.fruits li.apple { background: url("/images/apple.png"); } .fruits li.melon { background: url("/images/melon.png"); } .fruits li.banana { background: url("/images/banana.png"); } .fruits li.mango { background: url("/images/mango.png"); }

혹시 매번 반복마다 Index 값이 필요하다면 sass:list 모듈의 list.index() 함수를 사용할 수 있습니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
10
11
12
@use "sass:list"; $fruits: (apple, melon, banana, mango); .fruits { @each $fruit in $fruits { $i: list.index($fruits, $fruit); li:nth-child(#{$i}) { left: 50px * $i; } } }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
.fruits li:nth-child(1) { left: 50px; } .fruits li:nth-child(2) { left: 100px; } .fruits li:nth-child(3) { left: 150px; } .fruits li:nth-child(4) { left: 200px; }

동시에 여러 개의 List 데이터를 반복 처리할 수도 있습니다.
단, 각 데이터의 Length가 같아야 합니다.

SCSS:

SCSS
1
2
3
4
5
6
7
8
9
$apple: (apple, korea); $melon: (melon, china); $banana: (banana, japan); @each $fruit, $country in $apple, $melon, $banana { .box-#{$fruit} { background: url("/images/#{$country}.png"); } }

Compiled to:

CSS
1
2
3
4
5
6
7
8
9
.box-apple { background: url("/images/korea.png"); } .box-melon { background: url("/images/china.png"); } .box-banana { background: url("/images/japan.png"); }

Map 데이터를 반복할 경우 하나의 데이터에 두 개의 변수가 필요합니다.

SCSS
1
2
3
@each $key변수, $value변수 in 데이터 { // 반복 내용 }
SCSS
1
2
3
4
5
6
7
8
9
10
11
$fruits-data: ( apple: korea, melon: china, banana: japan ); @each $fruit, $country in $fruits-data { .box-#{$fruit} { background: url("/images/#{$country}.png"); } }
CSS
1
2
3
4
5
6
7
8
9
.box-apple { background: url("/images/korea.png"); } .box-melon { background: url("/images/china.png"); } .box-banana { background: url("/images/japan.png"); }

# @while

@while은 조건이 false로 평가될 때까지 내용을 반복합니다.
while 문과 유사하게 잘못된 조건으로 인해 컴파일 중 무한 루프에 빠질 수 있습니다.
사용을 권장하지 않습니다.

SCSS
1
2
3
@while 조건 { // 반복 내용 }
SCSS
1
2
3
4
5
6
7
8
$i: 6; @while $i > 0 { .item-#{$i} { width: 2px * $i; } $i: $i - 2; }
CSS
1
2
3
.item-6 { width: 12px; } .item-4 { width: 8px; } .item-2 { width: 4px; }

# 내장 모듈

Sass가 기본적으로 제공하는 내장 함수(Built-in Functions)는 내장 모듈(Built-in Modules)로 나뉘어 있습니다.
사용하려면 @use로 모듈을 먼저 가져오고, 네임스페이스를 붙여서 호출합니다.

SCSS
1
2
3
4
5
@use "sass:math"; div { width: math.div(100px, 3); }

map-get()이나 str-slice()처럼 네임스페이스 없이 호출하던 전역 내장 함수는 Dart Sass 1.80.0부터 지원이 중단되었고, Dart Sass 3.0.0에서 제거될 예정입니다.
기존 함수는 모두 각 모듈 안으로 옮겨졌으니 map.get(), string.slice()처럼 사용합니다.

모두 소개하지 않고, 주관적 경험에 의거해 필요하거나 필요할 수 있는 함수만 정리했습니다.
Sass Built-in Modules에서 모든 내장 함수를 확인할 수 있습니다.

  • []는 선택 가능한 인수(Argument)입니다.
  • Zero-based numbering을 사용하지 않습니다.

# sass:math

함수 설명
math.div($n1, $n2) 두 숫자를 나눈 값을 반환합니다.
math.percentage($n) 숫자(단위 무시)를 백분율로 변환합니다.
math.round($n) 정수로 반올림합니다.
math.ceil($n) 정수로 올림합니다.
math.floor($n) 정수로 내림(버림)합니다.
math.abs($n) 숫자의 절댓값을 반환합니다.
math.min($n...) 숫자 중 최솟값을 찾습니다.
math.max($n...) 숫자 중 최댓값을 찾습니다.
math.random([$limit]) 인수가 없으면 0부터 1 사이의 난수를, 있으면 1부터 $limit 사이의 정수를 반환합니다.
math.unit($n) 숫자의 단위를 반환합니다.
math.is-unitless($n) 숫자에 단위가 없는지 여부를 반환합니다.
math.compatible($n1, $n2) 두 개의 숫자가 연산 가능한지 여부를 반환합니다.

# sass:string

함수 설명
string.unquote($string) 문자에서 따옴표를 제거합니다.
string.quote($string) 문자에 따옴표를 추가합니다.
string.length($string) 문자의 길이를 반환합니다.
string.insert($string, $insert, $index) 문자의 index번째에 특정 문자를 삽입합니다.
string.index($string, $substring) 문자에서 특정 문자의 첫 index를 반환합니다.
string.slice($string, $start-at, [$end-at]) 문자에서 특정 문자(몇 번째 글자부터 몇 번째 글자까지)를 추출합니다.
string.split($string, $separator) 문자를 구분자로 나눠 List로 반환합니다.
string.to-upper-case($string) 문자를 대문자로 변환합니다.
string.to-lower-case($string) 문자를 소문자로 변환합니다.

# sass:color

함수 설명
color.mix($color1, $color2, [$weight]) 두 개의 색을 섞습니다.
color.adjust($color, $channel: $amount) 채널 값을 더하거나 뺍니다.
color.scale($color, $channel: $percent) 채널이 가진 남은 범위를 기준으로 비율만큼 조절합니다.
color.change($color, $channel: $value) 채널 값을 지정한 값으로 바꿉니다.
color.channel($color, $channel, $space) 특정 색상 공간의 채널 값을 반환합니다.
color.grayscale($color) 색상을 회색으로 변환합니다.
color.invert($color) 색상을 반전시킵니다.
color.complement($color) 보색을 반환합니다.
color.space($color) 색상이 속한 색상 공간의 이름을 반환합니다.
color.to-space($color, $space) 색상을 다른 색상 공간으로 변환합니다.

$channel에는 $red, $green, $blue, $hue, $saturation, $lightness, $alpha 등을 사용할 수 있습니다.

# sass:list

모든 List 내장 함수는 기존 List 데이터를 갱신하지 않고 새 List 데이터를 반환합니다.
모든 List 내장 함수는 Map 데이터에서도 사용할 수 있습니다.

함수 설명
list.length($list) List의 개수를 반환합니다.
list.nth($list, $n) List에서 n번째 값을 반환합니다.
list.set-nth($list, $n, $value) List에서 n번째 값을 다른 값으로 변경합니다.
list.append($list, $value, [$separator]) List에 값을 추가합니다.
list.join($list1, $list2, [$separator]) 두 개의 List를 하나로 결합합니다.
list.zip($lists...) 여러 List들을 하나의 다차원 List로 결합합니다.
list.index($list, $value) List에서 특정 값의 index를 반환합니다.
list.separator($list) List의 구분자를 반환합니다.

# sass:map

모든 Map 내장 함수는 기존 Map 데이터를 갱신하지 않고 새 Map 데이터를 반환합니다.

함수 설명
map.get($map, $key) Map에서 특정 key의 value를 반환합니다.
map.set($map, $key, $value) Map에서 특정 key의 value를 변경합니다.
map.merge($map1, $map2) 두 개의 Map을 병합하여 새로운 Map을 만듭니다.
map.remove($map, $keys...) Map에서 특정 key를 제거합니다.
map.has-key($map, $key) Map에 특정 key가 있는지 여부를 반환합니다.
map.keys($map) Map에서 모든 key를 List로 반환합니다.
map.values($map) Map에서 모든 value를 List로 반환합니다.

# sass:meta

함수 설명
meta.variable-exists($name) 변수가 현재 범위에 존재하는지 여부를 반환합니다.(인수는 $ 없이 변수의 이름만 사용합니다)
meta.mixin-exists($name) Mixin이 존재하는지 여부를 반환합니다.
meta.function-exists($name) 함수가 존재하는지 여부를 반환합니다.
meta.type-of($value) 값의 데이터 종류를 반환합니다.
meta.inspect($value) 값을 사람이 읽을 수 있는 문자로 반환합니다.
meta.load-css($url, [$with]) 모듈의 CSS를 현재 위치에 출력합니다. 함수가 아니라 Mixin이므로 @include meta.load-css("theme")처럼 사용합니다.

# 마이그레이션

이 글에서 살펴본 것처럼 Dart Sass는 여러 기존 문법의 지원을 중단했습니다.
아직 동작하는 문법도 컴파일할 때마다 경고가 출력되고, 예정된 버전에서 제거됩니다.

기존 방식 현재 방식 지원 중단 제거 예정
@import "config" @use "config", @forward "config" 1.80.0 3.0.0
map-get($map, $key) 같은 전역 내장 함수 map.get($map, $key) 같은 내장 모듈의 함수 1.80.0 3.0.0
lighten($color, 10%) 같은 기존 색상 함수 color.adjust($color, $lightness: 10%) 1.79.0 2.0.0
$width / 2 math.div($width, 2), calc($width / 2) 1.33.0 2.0.0
if($cond, $a, $b) if(sass($cond): $a; else: $b) 1.95.0 미정
선언하지 않은 변수에 !global 최상위에 먼저 선언 1.17.2 2.0.0

이런 코드를 사용하는 프로젝트는 Sass Migrator로 한 번에 바꿀 수 있습니다.

BASH
1
npm i -g sass-migrator
설치

module@import@use@forward로 바꾸고 전역 내장 함수에 네임스페이스를 붙입니다.
--migrate-deps 옵션을 추가하면 진입점 파일이 가져오는 파일들까지 함께 변환합니다.

BASH
1
sass-migrator module --migrate-deps scss/main.scss

division/ 나누기 연산을 math.div()로, colorlighten() 같은 기존 색상 함수를 color.adjust()로, if-function은 기존 if() 구문을 CSS if() 구문으로 바꿉니다.

BASH
1
2
3
sass-migrator division --migrate-deps scss/main.scss sass-migrator color --migrate-deps scss/main.scss sass-migrator if-function --migrate-deps scss/main.scss

변환한 결과는 되돌릴 수 없으니, Git으로 변경 사항을 확인할 수 있는 상태에서 실행하세요.