@mixin & @include 디렉티브를 사용한 스타일 재사용
mixin은 CSS 내에서 공통으로 사용되는 스타일을 재사용할 수 있는 기능이다.
함수처럼 중복되는 스타일을 분리하여 별도의 이름을 붙여 사용할 수 있고, 파라미터를 정의하여 값을 전달할 수도 있다.
mixin은 SCSS 문법 기준으로 아래와 같이 정의한다.
scss 문법 기준으로 작성했으나, 중괄호({})와 세미콜론(;)을 제외하면 sass 문법도 동일하게 사용이 가능하다.
파라미터 이름 앞에는 $ 기호를 붙여주고, 기본값을 지정하고자 하는 경우에는 $파라미터명: 값 형태로 작성하면 된다.
@mixin 디렉티브를 이용하여 mixin을 정의하고, 정의한 mixin은 @include 디렉티브로 사용할 수 있다.
// 기본 mixin 정의
@mixin mixin-name {
// style 작성
}
// 인자를 갖는 mixin
@mixin mixin-name($param-name, ...) {
// style 작성
}
// 인자에 기본값을 갖는 mixin
@mixin mixin-name($param-name: 'default_value', ...) {
// style 작성
}
// 정의한 mixin 사용
div {
@include mixin-name(10px, #1187cf, '100% - 10px');
}
// 일부 인자만 넘기는 경우
div {
@include mixin-name((size: 80px, weight: bold)...);
}
실제 사용 예시를 통해 알아보자.
$primary: #786450;
.user-photo-circle-outer {
border-radius: 50%;
width: 92px;
height: 92px;
margin: 0 auto;
top: -6px;
border: 1px solid $primary;
position: absolute;
box-sizing: border-box;
border-right-color: transparent;
right: -6px;
}
.user-photo-circle-inner {
border-radius: 50%;
width: 86px;
height: 86px;
margin: 0 auto;
top: -3px;
border: 1px solid $primary;
position: absolute;
box-sizing: border-box;
border-left-color: transparent;
left: -3px;
}
.user-photo-circle-outer와 .user-photo-circle-inner는 width, height 크기만 다르고 중복되는 스타일이 많다.
물론 겹치는 스타일을 별도의 클래스 선택자로 분리하는 방법도 있겠지만, mixin을 적용하여 해결하는 방법도 있다.
아래 예제에서는 width와 height의 값인 size라는 이름의 인자로 받는 mixin을 선언했고, size 값을 이용하여 top 값을 계산했다.
$primary: #786450;
@mixin photo-circle($size) {
border-radius: 50%;
width: $size;
height: $size;
margin: 0 auto;
top: calc((80px - #{$size}) / 2);
border: 1px solid $primary;
position: absolute;
box-sizing: border-box;
}
.user-photo-circle-outer {
@include photo-circle(92px);
border-right-color: transparent;
right: -6px;
}
.user-photo-circle-inner {
@include photo-circle(86px);
border-left-color: transparent;
left: -3px;
}
@extend 디렉티브를 사용한 스타일 확장 / & 선택자
@extend 디렉티브는 mixin과 비슷하게 사용할 수 있는 옵션으로, 이미 정의된 스타일을 특정 선택자 스타일에 확장시키는 기능을 제공한다.
이게 무슨 소리냐? 알기 쉽게 예시로 이해하자.
특정 HTML 요소에 여러 클래스에 정의된 스타일을 동시에 적용하고자 하는 경우에는 다음과 같이 클래스 이름을 줄줄이 적어줘야 한다.
적용하고자 하는 스타일 클래스가 늘어날 수록 마크업 가독성이 떨어진다.
<div class="button-basic button-report">
<!-- 내용 -->
</div>
@extend 디렉티브를 사용하여 스타일을 확장해 주면 아래처럼 몸통이 되는 클래스 이름 하나만 적어주면 돼서 마크업에 용이하다는 장점이 있다.
.button-basic {
border: none;
padding: 15px 30px;
text-align: center;
font-size: 16px;
cursor: pointer;
}
.button-report {
@extend .button-basic;
background-color: red;
}
<div class="button-report">
<!-- 내용 -->
</div>
만약에 @extend 디렉티브에 지정한 선택자가 올바르지 않은 경우에는 컴파일 오류가 발생한다.
다음 코드는 선택자 .style에 선언된 스타일이 없기 때문에 오류가 발생하는 것을 확인할 수 있다.
.selected {
@extend .style;
}
이러한 경우에는 !optional 플래그를 선택자 뒤에 추가하여 오류를 방지할 수 있다.
.selected {
@extend .style !optional;
}
자체 규칙 선택자인 %를 사용하여 스타일을 정의하는 방법도 있다.
% 선택자를 사용하여 정의한 스타일은 extend되지 않는 경우에는 컴파일 결과물인 css 파일 내에 포함되지 않는다.
%message-shared {
border: 1px solid #ccc;
padding: 10px;
color: #333;
}
// 컴파일된 결과물에 출력되지 않음
%equal-heights {
display: flex;
flex-wrap: wrap;
}
.success {
@extend %message-shared;
border-color: green;
}
특정 경우에만 mixin 내에 스타일을 추가할 수 있는 @content
@content 디렉티브는 @mixin 디렉티브와 함께 사용할 수 있는 기능으로, mixin 내부에 추가적인 스타일을 동적으로 추가해주고 싶을 때 유용하게 사용할 수 있다.
다음과 같이 mixin을 include할 때, 중괄호({ }) 내에 추가할 스타일을 작성 + 정의한 mixin 내부에 @content; 코드를 추가해 주면 컴파일 시에 @content 위치에 추가로 넘겨준 스타일이 삽입된다.
@include 믹스인_이름(...) {
// mixin에 추가할 스타일 작성
};
위에서 사용한 예제 코드를 다시 가지고 와서 실습을 해보자.
$primary: #786450;
@mixin photo-circle($size) {
border-radius: 50%;
width: $size;
height: $size;
margin: 0 auto;
top: calc((80px - #{$size}) / 2);
border: 1px solid $primary;
position: absolute;
box-sizing: border-box;
}
.user-photo-circle-outer {
@include photo-circle(92px);
border-right-color: transparent;
right: -6px;
}
.user-photo-circle-inner {
@include photo-circle(86px);
border-left-color: transparent;
left: -3px;
}
mixin으로 두 클래스의 중복된 스타일을 분리하는 데까지는 성공했지만, 두 클래스는 left/right와 border-color 스타일 속성을 각각 다르게 갖고 있다.
@content 디렉티브를 사용하여 다음과 같이 스타일을 작성해도 위 예제와 동일하게 동작한다.
$primary: #786450;
@mixin photo-circle($size) {
border-radius: 50%;
width: $size;
height: $size;
margin: 0 auto;
top: calc((80px - #{$size}) / 2);
border: 1px solid $primary;
position: absolute;
box-sizing: border-box;
@content;
}
.user-photo-circle-outer {
@include photo-circle(92px) {
border-right-color: transparent;
right: -6px;
};
}
.user-photo-circle-inner {
@include photo-circle(86px) {
border-left-color: transparent;
left: -3px;
};
}
외부 스타일 파일을 가져오기 위한 @import vs @use
@include 외에도 sass/scss에서는 @import, @use 등의 다양한 디렉티브를 제공한다.
@import 디렉티브는 다른 style 정의 파일을 import하는 데에 사용한다.
다음과 같이 css, scss, sass 확장자를 갖는 style 파일의 내용을 현재 파일 내에 import하여 사용할 수 있다.
부분 스타일 파일의 언더바(_)와 scss, sass 확장자의 경우에는 생략이 가능하며, 상대 경로(./) 또한 생략이 가능하다.
@import "variables"; // 확장자 생략 가능
@import "colors"; // _ 생략 가능(_colors.scss import)
@import "reset.css"; // reset.css import
// url import
@import "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.3/styles/atom-one-dark-reasonable.min.css";
@import와 유사한 기능을 하는 @use 디렉티브도 있다.
두 디렉티브는 스타일 파일을 import할 때 사용한다는 점에서는 동일하지만 동작 방식에서의 차이가 있다.
@use 디렉티브는 네임스페이스 방식으로 동작한다.
@import의 경우에는 가져오는 파일의 내용 전체를 현재 파일에 import하기 때문에 선언한 변수나 함수의 이름이 같다면 충돌이 발생할 수 있다.
@use는 기본적으로 가져오는 파일의 이름을 네임스페이스로 구분하여 import하기 때문에 충돌의 위험이 없다.
// _colors.scss import && namespace: colors
@use 'colors';
.box {
background-color: colors.$blue; // namespace.$variable
}
// _colors.scss import && namespace: cls
@use 'colors' as cls;
.box {
background-color: cls.$blue;
}
// not using namespace
@use 'colors' as *;
.box {
background-color: $blue;
}
따라서 @use로 import한 파일의 변수나 mixin, 함수에 접근하기 위해서는 네임스페이스명.* 형태로 접두사를 붙여 사용해야 한다.
as 키워드를 사용하여 원하는 네임스페이스 이름으로 설정하는 것도 가능하다.
as *로 별칭을 설정하는 경우에는 루트 네임스페이스에 모듈이 추가되므로 접두사를 명시하지 않아도 된다.
그 외에도 @use는 다음과 같은 특징을 갖는다.
- 파일 범위 내에서만 import한 변수, 함수 사용 가능
전역 스타일로 import하지 않으므로 특정 모듈 파일의 변수를 사용하기 위해서는 필요할 때마다 import가 필요하다.
- 변수나 함수, mixin 이름 앞에 _(언더바)나 -(하이픈)을 사용하는 경우에는 private한 값으로 간주하여 import하지 않음
// _colors.scss
$_sky: #61dafb;
// _layout.scss
@use 'colors';
.link {
color: colors.$sky; // error!
}
(+ @import는 2022년도 이후로 deprecated된다는 소문이 있기 때문에 가급적 @use를 사용하도록 하자.)
반복적인 import를 효율적으로 하기 위한 @forward
위에서도 알아봤듯이 @use는 global style로서 스타일을 import하는 것이 아니기 때문에 각각의 파일에서 특정 스타일 파일을 사용해야 할 때마다 각각 import를 해야 하는 귀찮음이 있다.
특히 아래와 같이 여러 파일을 각각 @use 디렉티브로 import를 해준다면, import하는 파일의 개수가 늘어날 수록 상단에 불필요한 코드 라인이 여러 줄 생기기 때문에 비효율적이다.
@use 'colors';
@use 'fonts';
@use 'transitions';
이럴 때는 @forward 디렉티브를 사용해서 반복적인 import 구문을 하나의 import 구문으로 효율적으로 관리할 수 있다.
다음과 같이 함께 자주 사용되는 스타일 파일을 한꺼번에 포워딩해주는 별도의 파일을 생성한 후, 해당 파일을 import하여 사용하면 된다.
/* _total.scss */
@forward 'colors';
@forward 'default';
@forward 'layout';
/* styles.scss */
@use 'total';
/* another.scss */
@use 'total';
mixin에서의 calc() 함수 사용 + 기타 산술 연산자 사용
CSS에서는 calc() 함수를 사용하여 수학적 연산이 가능하다.
mixin에서도 calc() 함수 사용이 가능하지만, 함수 내에서 인자 값을 사용하기 위해서는 다음과 같이 #{$변수명} 기호로 감싸서 사용해야 한다.
@mixin photo-circle($size) {
border-radius: 50%;
width: $size;
height: $size;
margin: 0 auto;
top: calc((80px - #{$size}) / 2);
border: 1px solid $primary;
position: absolute;
box-sizing: border-box;
}
부분(partial) 스타일 파일 정의 및 사용
// 일반 스타일 파일명
reset.scss, global.scss
// 서브 모듈 파일명
_colors.scss, _palette.scss
sass/scss 스타일 파일명 앞에 언더바(_)를 붙이게 되면 해당 파일을 partial한 모듈로 간주한다.
모듈 파일은 그 자체로 컴파일의 대상이 되지 않으으로 별도의 파일로 컴파일되지 않으며, 스타일 파일에 import해서 사용하는 경우에만 컴파일의 대상이 된다.
만약, 컬러 정의(_colors.scss)나 기본 스타일(_default.scss) 등을 정의해야 한다면 서브 스타일 파일을 만들어서 사용하는 것이 좋다.
조건문(@if, @else if, @else, @while) 및 반복문(@each)
sass/scss 파일 내에서는 자바스크립트의 if~else문과 유사한 조건문을 사용할 수 있는 @if, @else if, @else 디렉티브와 while문과 유사한 @while 디렉티브를 제공한다.
if(@if, @else if, @else)문 관련 디렉티브는 일반적인 프로그래밍 언어와 동일하게 @if 조건 { 내용 } 형식으로 사용하며, 사용 예시는 다음과 같다.
@use 'colors';
$type: body;
body {
@if $type == body {
color: colors.$red;
} @else {
color: colors.$blue;
}
}
@while 디렉티브도 마찬가지로 @while 조건 { 내용 } 형식으로 사용하면 된다.
$i: 1;
@while $i < 4 {
.section#{$i} { background-color: lighten(blue, 15% * $i); }
$i: $i + 1;
}
또한 for문과 유사한 반복 구문을 사용할 수 있는 @each 디렉티브도 있다.
@each 디렉티브는 @each $사용할_변수명 in 항목1, 항목2, ... 처럼 순회할 항목과 함께 변수명을 지정하여 사용할 수 있다.
@each $type in fine, cloudy, rainy {
.item-#{$type} { background-image: url('/images/#{$type}.gif'); }
}
사용자 정의 함수 @function
자바스크립트의 function 키워드와 유사한 방식으로, @function 디렉티브를 사용하여 sass 파일 내에서 함수를 정의하고 호출할 수 있다.
사용하고자 하는 함수명 앞에 @function 디렉티브를 붙여 함수를 선언하고, 함수 내에서 값을 리턴할 때는 리턴하고자 하는 값 앞에 @return 디렉티브를 붙여 사용하면 된다.
$first-width: 5px;
$second-width: 5px;
@function adjust_width($n) {
@return $n * $first-width + ($n - 1) * $second-width;
}
#set_width {
padding-left: adjust_width(10); // 95px로 계산
}
주로 가로/세로 길이를 계산하거나 단위를 변경할 때 사용되는 식을 함수로 정의한 후, 여러 스타일 파일에서 import하여 유용하게 사용할 수 있다.
중첩이 가능한 @media
@media 디렉티브는 css에서 사용하는 방식과 동일한 기능을 제공하지만, 스타일 내부에 중첩이 가능하다는 점이 다르다.
@media를 스타일 내부에 중첩해서 사용할 경우, 선언된 위치의 선택자를 시작으로 루트 경로 수준까지 버블링된다.
예를 들어, 다음과 같은 중첩 media 문은 아래와 같이 변환된다.
/* before compiling */
@media screen {
.main {
@media (max-width: 600px) {
width: 100%;
}
@media (min-width: 700px) {
width: 70%;
}
}
}
/* after compiling */
@media screen and (max-width: 600px) {
.main {
width: 100%;
}
}
@media screen and (min-width: 700px) {
.main {
width: 70%;
}
}
어디서든 루트 경로처럼 스타일을 선언할 수 있는 @at-root
@at-root 디렉티브는 어디서든 루트 경로에서 선언한 것처럼 스타일을 선언할 수 있는 디렉티브이다.
@media 등의 디렉티브로 감싸져 있는 스타일 내에서 사용하더라도 디렉티브를 무시할 수 있는 강력한 기능을 제공한다.
(참고로 @mixin, @function 등의 함수성 디렉티브 내에서는 사용이 안되니 알아두자.)
제목이 난해해서 이해하기 힘들 수 있으니 예시 코드를 보면서 이해해 보자.
아래 코드에서는 @media 디렉티브 내에 #header와 .style 지시자로 스타일을 정의하고 있다.
@media (min-width: 1200px) {
#header {
height: 80px;
.style {
background-color: #fff;
@at-root (without: media) {
color: #808000;
}
}
}
}
min-width: 1200px을 조건으로 명시했기 때문에 viewport 사이즈가 1200px 이상인 경우에만 스타일이 적용된다.
만약 #header .style { } 영역 내에서 @at-root (without: 무시할 디렉티브)를 사용하여 스타일을 작성하는 경우에는 중첩된 디렉티브가 있더라도 디렉티브를 무시하고 마치 루트 경로에서 선언한 것처럼 스타일이 정의된다.
따라서 위 예제 코드는 아래와 같이 컴파일된다.
@media (min-width: 1200px) {
#header {
height: 80px;
}
#header .style {
background-color: #fff;
}
}
@at-root 디렉티브는 아래와 같이 디렉티브가 아닌 일반 중첩 스타일 내에서도 사용이 가능하다.
#header {
color: #808000;
background-color: #DB7093;
@at-root {
.style{
font-size: 20px;
font-style: bold;
color: #B8860B;
}
}
}
이런 경우에는 without 부분을 작성하지 않아도 되며, @at-root 안에서 선언한 스타일은 해당 스타일 파일의 루트 경로에서 선언한 것처럼 변환된다.
따라서 위 예제 코드와 같이 작성된 코드는 다음과 같이 컴파일된다.
#header {
color: #808000;
background-color: #DB7093;
}
.style {
font-size: 20px;
font-style: bold;
color: #B8860B;
}
디버깅 시 사용하는 @debug, @warn, @error
해당 디렉티브들은 컴파일이 일어날 때 실행되는 디렉티브로, 콘솔에서 확인이 가능하다.
@debug, @warn, @error 디렉티브 모두 용도는 다르나, 인자로 받은 표현식 혹은 스타일, 문자열 등을 콘솔에 출력한다는 점은 동일하다.
@debug는 디버깅을 위한 디렉티브로, 다음과 같이 로깅 용도로 사용할 수 있다.
$colors: (
blue: #c0392b,
black: #2980b9,
);
@debug 10px + 20px;
@debug $colors;
@warn은 경고를 발생시키기 위한 디렉티브로, 주로 조건문 내에서 특정 조건을 만족시키는 경우에 경고를 발생시키는 용도로 사용된다.
#header {
background-color: transparent;
color: #111;
display: flex;
@warn "This is warn message.";
}
@warn 10px + 20px;
@error는 에러를 발생시키기 위한 디렉티브로, @warn과 마찬가지로 주로 조건문 내에서 특정 조건을 만족시키는 경우에 에러를 발생시키는 용도로 사용된다.
#header {
@if map-has-key($colors, yellow) {
color: map-get($colors, yellow);
} @else {
@error 'yellow is not existed.';
}
}
콘솔 메시지를 통해 에러가 발생한 줄 번호와 오류 내용, 코드를 확인할 수 있다.
참고 링크
'Frontend > CSS' 카테고리의 다른 글
CSS - 속성/값 여부를 css 선택자로 사용하는 방법 (0) | 2024.12.16 |
---|---|
CSS - 가이드 영역을 간단히 만들고 싶다면? mix-blend-mode 알아보기 (0) | 2024.11.22 |
CSS - transition 속성 알아보기 (1) | 2022.08.11 |
CSS로 직접 그래프 만들기(bar, donut 그래프) + 애니메이션 효과 추가 (6) | 2021.08.20 |
CSS - ellipsis(..., 말 줄임표) 처리 (0) | 2021.08.19 |