CSS 전처리기 (Sass) 입문
우리는 지금까지 순수 CSS(Vanilla CSS)만으로 웹 페이지를 스타일링하고, 고급 기법들을 통해 코드의 효율성을 높이는 방법을 학습했습니다. CSS 변수와 calc()
함수는 CSS의 유연성을 크게 향상시켰지만, 여전히 순수 CSS만으로는 대규모 프로젝트에서 반복적인 작업을 줄이거나 복잡한 로직을 처리하는 데 한계가 있습니다. 예를 들어, 반복되는 CSS 속성을 줄이거나, 함수처럼 재사용 가능한 스타일 묶음을 만들거나, 선택자를 중첩하여 부모-자식 관계를 시각적으로 표현하는 것은 순수 CSS에서는 불가능합니다.
여기서 등장하는 것이 바로 CSS 전처리기(CSS Preprocessor) 입니다. CSS 전처리기는 CSS를 더욱 강력하게 만들어주는 스크립트 언어로, 우리가 작성한 코드를 컴파일(Compile)하여 브라우저가 이해할 수 있는 표준 CSS로 변환해 줍니다. 이를 통해 변수, 중첩, 믹스인(함수), 상속 등의 프로그래밍 개념을 CSS에 도입할 수 있어, 개발 생산성을 크게 높이고 유지보수성을 향상시킬 수 있습니다.
가장 널리 사용되는 CSS 전처리기에는 Sass(Syntactically Awesome Style Sheets), Less, Stylus 등이 있습니다. 이 장에서는 Sass를 중심으로, 그 중에서도 표준 CSS와 가장 유사한 문법을 가진 SCSS(Sassy CSS) 를 사용하여 CSS 전처리기의 핵심 기능을 학습할 것입니다.
전처리기란 무엇이며 왜 필요한가?
CSS 전처리기(CSS Preprocessor): CSS를 확장하는 스크립트 언어입니다. 개발자가 전처리기 문법으로 코드를 작성하면, 전처리기가 이를 일반적인 CSS 코드로 변환(컴파일)해 줍니다. 브라우저는 이 변환된 CSS 파일을 해석하여 웹 페이지를 렌더링합니다.
필요성
- 변수(Variables): 색상, 폰트 크기 등 자주 사용되는 값을 변수로 정의하여 한 곳에서 관리할 수 있습니다. (순수 CSS 변수보다 기능이 강력하고 이전 버전의 브라우저도 지원)
- 중첩(Nesting): HTML 구조처럼 CSS 선택자를 중첩하여 작성할 수 있어 코드의 가독성이 향상됩니다.
- 믹스인(Mixins): 반복적으로 사용되는 CSS 속성 묶음을 함수처럼 정의하여 필요할 때마다 호출하여 사용할 수 있습니다.
- 상속(Inheritance) / 확장(Extend): 특정 선택자의 스타일을 다른 선택자에 상속시켜 코드 중복을 줄일 수 있습니다.
- 부분 파일(Partials) 및 임포트(Import): CSS 파일을 여러 작은 파일로 분리하고, 필요한 곳에서
@import
규칙으로 불러와 관리할 수 있습니다. 이는 SMACSS와 같은 CSS 아키텍처를 구현하는 데 매우 유용합니다. - 연산(Operations): 사칙연산은 물론, 색상 함수 등 다양한 함수를 사용하여 복잡한 계산을 수행할 수 있습니다.
Sass (SCSS 문법) 설치 및 사용 준비
Sass는 Ruby 또는 Node.js 환경에서 실행됩니다. 현대 웹 개발에서는 Node.js 기반의 node-sass
또는 sass
패키지를 사용하는 것이 일반적입니다.
설치 과정 (Node.js 및 npm 필요)
- Node.js 설치 확인: 터미널/명령 프롬프트에서
node -v
와npm -v
를 입력하여 버전이 표시되는지 확인합니다. (설치되어 있지 않다면 Node.js 공식 홈페이지에서 설치) - 프로젝트 폴더 생성 및 이동:
mkdir my-sass-project cd my-sass-project
- npm 프로젝트 초기화:
(npm init -y
package.json
파일이 생성됩니다.) - Sass 컴파일러 설치:
여기서는 편의상 전역 설치를 권장합니다.npm install -g sass # 전역 설치 (어디서든 sass 명령 사용 가능) # 또는 npm install --save-dev sass # 프로젝트 내 설치 (프로젝트에서만 사용)
Sass 컴파일 명령
SCSS 파일(*.scss
)을 표준 CSS 파일(*.css
)로 컴파일하는 기본적인 명령입니다.
sass input.scss output.css
자동 컴파일 (Watch Mode): 파일을 저장할 때마다 자동으로 컴파일되도록 watch
모드를 사용하는 것이 개발에 훨씬 편리합니다.
sass --watch input.scss:output.css
# 예시: sass --watch scss/main.scss:css/style.css
이 명령을 실행하면 터미널은 계속 대기 상태가 되고, input.scss
파일에 변경이 감지될 때마다 output.css
를 자동으로 업데이트합니다.
Sass (SCSS) 핵심 기능
이제 SCSS의 주요 기능들을 살펴봅시다.
변수 (Variables)
CSS 변수와 유사하지만, Sass 변수는 $foo: value;
형식으로 선언하며, 컴파일 시점에 값이 결정되어 브라우저 호환성을 신경 쓸 필요가 없습니다.
/* _variables.scss (보통 별도의 파일로 관리) */
$primary-color: #3498db;
$text-color: #333;
$font-stack: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
$base-padding: 15px;
/* style.scss */
body {
font-family: $font-stack;
color: $text-color;
background-color: #f0f2f5;
}
.button {
background-color: $primary-color;
padding: $base-padding;
color: white;
border: none;
border-radius: 5px;
}
중첩 (Nesting)
HTML 구조처럼 CSS 선택자를 중첩하여 작성하여 코드의 계층 구조를 명확하게 보여줍니다.
/* SCSS */
nav {
ul {
margin: 0;
padding: 0;
list-style: none;
li {
display: inline-block;
margin-right: 10px;
a {
display: block;
padding: 5px 10px;
text-decoration: none;
color: #333;
&:hover { /* 부모 선택자(&) */
background-color: lightgray;
}
}
}
}
}
컴파일된 CSS:
/* CSS */
nav ul {
margin: 0;
padding: 0;
list-style: none;
}
nav ul li {
display: inline-block;
margin-right: 10px;
}
nav ul li a {
display: block;
padding: 5px 10px;
text-decoration: none;
color: #333;
}
nav ul li a:hover {
background-color: lightgray;
}
&
(부모 선택자 참조): 중첩된 규칙 내에서 부모 선택자를 참조할 때 사용합니다.:hover
,::before
등 가상 클래스/요소나 클래스 조합 시 유용합니다.
믹스인 (Mixins)
재사용 가능한 CSS 코드 블록을 정의하는 기능입니다. 함수처럼 인자를 받아 동적인 스타일을 생성할 수도 있습니다. @mixin
으로 정의하고, @include
로 사용합니다.
/* SCSS */
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
border-radius: $radius;
}
.container {
@include flex-center; /* 믹스인 사용 */
min-height: 200px;
background-color: #f9f9f9;
}
.card {
width: 200px;
height: 150px;
background-color: white;
@include border-radius(10px); /* 인자와 함께 믹스인 사용 */
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
컴파일된 CSS:
/* CSS */
.container {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
background-color: #f9f9f9;
}
.card {
width: 200px;
height: 150px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
@mixin
은 특히 벤더 프리픽스(vendor prefix)가 필요한 속성(transform
,transition
,animation
등)을 효율적으로 관리하는 데 매우 유용합니다.
상속/확장 (@extend
)
하나의 선택자의 CSS 속성을 다른 선택자에게 "상속"시키는 기능입니다. 코드 중복을 줄이는 데 사용됩니다.
/* SCSS */
.message {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.message--success {
@extend .message; /* .message의 모든 스타일을 상속받음 */
background-color: #d4edda;
color: #155724;
border-color: #c3e6cb;
}
.message--error {
@extend .message;
background-color: #f8d7da;
color: #721c24;
border-color: #f5c6cb;
}
컴파일된 CSS
/* CSS */
.message, .message--success, .message--error { /* 선택자들이 그룹화됨 */
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.message--success {
background-color: #d4edda;
color: #155724;
border-color: #c3e6cb;
}
.message--error {
background-color: #f8d7da;
color: #721c24;
border-color: #f5c6cb;
}
-
@extend
는placeholder
선택자(%placeholder-name
)와 함께 사용될 때 더 강력합니다.placeholder
는@extend
될 때만 CSS로 컴파일되어 불필요한 클래스를 생성하지 않습니다.%common-button-style { padding: 10px 15px; border-radius: 5px; cursor: pointer; } .btn-submit { @extend %common-button-style; background-color: blue; color: white; }
부분 파일 (Partials) 및 임포트 (@import
)
CSS 파일을 여러 개의 작은 모듈로 분리하여 관리하고, @import
규칙을 사용하여 다른 SCSS 파일로 가져올 수 있습니다. 부분 파일은 파일명 앞에 언더스코어(_
)를 붙여서 명명합니다 (예: _variables.scss
). 언더스코어는 해당 파일이 독립적으로 CSS로 컴파일되지 않고, 다른 SCSS 파일에 임포트될 용도로 사용됨을 의미합니다.
/* _variables.scss */
$font-size-base: 16px;
$color-main: #3498db;
/* _mixins.scss */
@mixin button-style {
padding: 10px 20px;
border-radius: 5px;
}
/* style.scss (메인 파일) */
@import 'variables'; /* _variables.scss 파일을 가져옴 (언더스코어 생략 가능) */
@import 'mixins'; /* _mixins.scss 파일을 가져옴 */
body {
font-size: $font-size-base;
color: $color-main;
}
.my-button {
@include button-style;
background-color: $color-main;
color: white;
}
이러한 모듈화는 SMACSS와 같은 CSS 아키텍처를 구현하는 데 매우 효과적입니다.
연산 (Operations)
Sass는 숫자, 색상 등 다양한 타입의 값에 대한 산술 연산을 지원합니다.
/* SCSS */
$base-font-size: 16px;
$line-height: 1.5;
$margin-unit: 10px;
body {
font-size: $base-font-size;
line-height: $line-height;
}
.box {
width: 100px + 50px; /* 150px */
height: 200px / 2; /* 100px */
padding: $margin-unit * 2; /* 20px */
margin: $margin-unit - 5px; /* 5px */
font-size: $base-font-size * 1.2; /* 19.2px */
}
/* 색상 연산 */
$color-primary: #3498db;
$color-darker: darken($color-primary, 10%); /* 더 어두운 색상 */
$color-lighter: lighten($color-primary, 15%); /* 더 밝은 색상 */
.header {
background-color: $color-darker;
color: white;
}
.footer {
background-color: $color-lighter;
}
실습: Sass로 반응형 블로그 헤더 만들기
이전 장에서 학습한 아키텍처와 방법론, 그리고 이번 장에서 배운 Sass 기능을 활용하여 반응형 헤더 컴포넌트를 만들어 봅시다.
-
프로젝트 폴더 구조
`web-dev-practice/ ├── sass_header.html └── scss/ ├── _variables.scss ├── _mixins.scss ├── _base.scss ├── _layout.scss ├── _header.scss └── style.scss (메인 SCSS 파일) └── css/ └── style.css (컴파일될 파일)
-
sass_header.html
파일 작성sass_header.html <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sass 반응형 헤더 실습</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <header class="main-header"> <div class="main-header__logo"> <a href="#">My Blog</a> </div> <nav class="main-header__nav main-nav"> <ul class="main-nav__list"> <li class="main-nav__item"><a href="#" class="main-nav__link">홈</a></li> <li class="main-nav__item"><a href="#" class="main-nav__link">카테고리</a></li> <li class="main-nav__item"><a href="#" class="main-nav__link">글쓰기</a></li> <li class="main-nav__item"><a href="#" class="main-nav__link">마이페이지</a></li> </ul> <button class="main-nav__toggle"> <span></span> <span></span> <span></span> </button> </nav> </header> <main class="content-wrapper"> <h1>반응형 웹과 Sass의 조화</h1> <p>이 페이지는 Sass를 이용하여 헤더를 만들었습니다. 화면 크기를 줄여보세요!</p> <p>Sass의 변수, 중첩, 믹스인 등의 기능으로 더욱 효율적인 CSS 개발이 가능합니다.</p> <div style="height: 1000px; background-color: #ecf0f1; padding: 20px;"> 페이지 스크롤 영역 </div> </main> <footer> <p>© 2025 Sass Practice.</p> </footer> </body> </html>
-
scss/style.scss
파일 작성 (메인 SCSS 파일)scss/style.scss // style.scss (메인 파일) // 1. 변수 정의 @import 'variables'; // 2. 믹스인 정의 @import 'mixins'; // 3. 기본 스타일 (Base) @import 'base'; // 4. 레이아웃 스타일 (Layout) @import 'layout'; // 5. 모듈/컴포넌트 스타일 (Header 모듈) @import 'header';
-
scss/_variables.scss
파일 작성scss/_variables.scss // _variables.scss $primary-color: #3498db; $text-color: #333; $bg-color: #f0f2f5; $header-bg: #2c3e50; $nav-link-color: white; $nav-link-hover-color: #1abc9c; // 터쿼이즈 $mobile-breakpoint: 768px; // 모바일에서 태블릿으로 넘어가는 기준 $tablet-breakpoint: 1024px; // 태블릿에서 데스크톱으로 넘어가는 기준
-
scss/_mixins.scss
파일 작성scss/_mixins.scss // _mixins.scss @mixin desktop-only { @media screen and (min-width: #{$tablet-breakpoint}) { @content; // 이 믹스인이 사용되는 곳의 CSS 내용을 포함 } } @mixin tablet-and-up { @media screen and (min-width: #{$mobile-breakpoint}) { @content; } } @mixin mobile-only { @media screen and (max-width: #{$mobile-breakpoint - 1}) { @content; } } // Flexbox 중앙 정렬 믹스인 @mixin flex-center { display: flex; justify-content: center; align-items: center; }
#{$변수이름}
: 미디어 쿼리 등에서 변수 값을 문자열로 사용해야 할 때 (인터폴레이션) 사용합니다.
-
scss/_base.scss
파일 작성scss/_base.scss // _base.scss * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: $bg-color; color: $text-color; line-height: 1.6; } a { text-decoration: none; color: $text-color; } ul { list-style: none; } img { max-width: 100%; height: auto; display: block; }
-
scss/_layout.scss
파일 작성scss/_layout.scss // _layout.scss .content-wrapper { max-width: 1200px; margin: 20px auto; padding: 20px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { color: $primary-color; margin-bottom: 15px; } p { margin-bottom: 10px; } footer { text-align: center; background-color: #ccc; padding: 15px; margin-top: 50px; color: #555; }
-
scss/_header.scss
파일 작성 (BEM + Sass 기능)scss/_header.scss // _header.scss .main-header { background-color: $header-bg; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 2px 5px rgba(0,0,0,0.2); &__logo { // .main-header__logo a { color: $nav-link-color; font-size: 1.8em; font-weight: bold; transition: color 0.2s ease; &:hover { color: $nav-link-hover-color; } } } &__nav { // .main-header__nav (BEM 요소인 동시에 .main-nav 모듈의 시작) .main-nav__list { display: flex; // 기본은 가로 메뉴 gap: 25px; .main-nav__item { // .main-nav__item .main-nav__link { // .main-nav__link color: $nav-link-color; font-weight: 500; padding: 8px 0; transition: color 0.2s ease; &:hover { color: $nav-link-hover-color; } } } } .main-nav__toggle { // .main-nav__toggle (모바일 햄버거 메뉴 버튼) display: none; // 기본은 숨김 background: none; border: none; cursor: pointer; padding: 10px; span { display: block; width: 25px; height: 3px; background-color: $nav-link-color; margin: 5px 0; transition: all 0.3s ease; } } } // 모바일 사이즈 이하에서 내비게이션 변경 @include mobile-only { // 믹스인 사용 flex-direction: column; // 세로 정렬 align-items: flex-start; padding: 10px 20px; .main-header__logo { width: 100%; text-align: center; margin-bottom: 10px; } .main-header__nav { width: 100%; text-align: center; .main-nav__list { flex-direction: column; // 메뉴 세로 정렬 width: 100%; gap: 0; // display: none; /* JavaScript로 토글될 예정 */ .main-nav__item { border-top: 1px solid rgba(255, 255, 255, 0.1); &:last-child { border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .main-nav__link { padding: 12px 0; width: 100%; &:hover { background-color: rgba(255, 255, 255, 0.1); } } } } .main-nav__toggle { display: block; // 햄버거 메뉴 버튼 표시 position: absolute; right: 20px; top: 15px; } } } // 특정 조건에서 토글 메뉴 활성화 (JavaScript와 연동) &.nav-active { .main-header__nav { .main-nav__list { display: flex; // 모바일에서 숨겨진 메뉴를 보이도록 } .main-nav__toggle { span:nth-child(1) { transform: translateY(8px) rotate(45deg); } span:nth-child(2) { opacity: 0; } span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); } } } } }
- 주의: JavaScript로
.main-nav__list
의display: none;
을 토글하는 기능을 추가해야 모바일 햄버거 메뉴가 작동합니다. 이 예시에서는 CSS만으로 반응형 레이아웃 변화를 보여주는 데 중점을 둡니다.
- 주의: JavaScript로
-
Sass 컴파일 및 결과 확인
- 프로젝트 루트 폴더(
my-sass-project
)에서 터미널/명령 프롬프트를 열고 다음 명령을 실행합니다:sass --watch scss/style.scss:css/style.css
scss/style.scss
및 관련 파일들을 저장할 때마다css/style.css
파일이 자동으로 업데이트되는 것을 확인합니다.sass_header.html
파일을 웹 브라우저에서 열어봅니다.- 브라우저 창의 너비를 조절해 보세요.
- 데스크톱/태블릿 크기에서는 내비게이션 메뉴가 가로로 보일 것입니다.
- 모바일 크기(
767px
이하)에서는 헤더가 세로로 정렬되고, 내비게이션 메뉴가 사라지며 (현재 CSS 상으로는 보이지만, 실제로는 JavaScript로 숨김 처리), 햄버거 메뉴 버튼이 나타나는 것을 볼 수 있습니다.
- 프로젝트 루트 폴더(
이번 장에서는 CSS 코드의 생산성과 유지보수성을 혁신적으로 향상시키는 도구인 CSS 전처리기(Preprocessor) 에 대해 학습했습니다. 특히 가장 널리 사용되는 Sass(SCSS 문법) 를 중심으로 다음 핵심 기능들을 익혔습니다.
$변수
: 재사용 가능한 값을 정의하여 일관된 디자인과 손쉬운 스타일 변경을 가능하게 합니다.- 중첩 (
{}
내부 선택자,&
부모 참조): HTML 구조처럼 CSS 선택자를 중첩하여 작성하여 코드의 가독성을 높입니다. @mixin
과@include
: 반복적으로 사용되는 CSS 코드 블록을 함수처럼 정의하고 재사용하여 코드 중복을 줄입니다.@extend
: 특정 선택자의 스타일을 다른 선택자에 상속시켜 코드 효율성을 높입니다.- 부분 파일 (
_파일.scss
)과@import
: CSS 파일을 모듈화하여 관리하고, 필요한 곳에서 불러와 체계적인 프로젝트 구조를 만듭니다. - 연산: 숫자 및 색상 값에 대한 다양한 연산을 통해 동적인 스타일을 생성합니다.
이러한 Sass의 기능들은 순수 CSS의 한계를 극복하고, 대규모 프로젝트에서 CSS를 더 체계적이고 효율적으로 작성할 수 있도록 돕습니다. 이제 여러분은 CSS 개발 워크플로우를 한 단계 더 발전시킬 준비가 되었습니다.
이것으로 4장 "CSS 고급 기법"의 모든 내용을 마칩니다. 여러분은 이제 CSS를 이용해 동적인 효과를 부여하고, 변수와 계산식을 활용하며, 체계적인 방법론과 전처리기를 적용하여 더 효율적이고 관리하기 쉬운 CSS 코드를 작성할 수 있게 되었습니다.