2020年の冬からSassが社内標準として採用されました。
最初は戸惑いましたが、慣れるとCSSを書くのがつらくなるぐらい便利です。
Sassのごく一部の機能しか使っていないな、と思ったので書き方をまとめてみました。
目次
Sassについて
Sass(Syntactically Awesome StyleSheet)はCSSのメタ言語です。CSSのメタ言語をCSSプリプロセッサと呼んだりします。
メタ言語は言語を定義したり、言語についての情報を記述したりするための言語です。CSSの機能を拡張した言語の1つがSassと言えます。
SassはCSSに変換(コンパイル)する必要があります。環境構築が面倒という方もGUIコンパイラやVSCodeのプラグインなどを使えば、気軽にSassを導入できます。
GUIコンパイラの使い方については、こちらの記事を参考にしてみてください。
Sass(SCSS)を始めよう!「Prepros(プリプロス)」で簡単にSass環境を作ろう!(基礎編)
SASSとSCSS
SassにはSASS、SCSSと2種類の記法があります。それぞれ拡張子が.sass、.scssです。
SASS記法は以下のように書くことができます。
・波括弧({})の代わりにインデントを使用する
・セミコロン(;)を省略できる
しかしCSSとの互換性がないためあまり普及せず、SCSS記法がよく使われている印象です。
1 2 3 4 5 6 7 8 9 | .box background: red height: 80px width: 100px .title font-size: 14px letter-spacing: 0.1em |
1 2 3 4 5 6 7 8 9 10 11 | .box { background: red; height: 80px; width: 100px; .title { font-size: 14px letter-spacing: 0.1em } } |
上がSASS記法で、下がSCSS記法のコードです。SCSS記法の方がCSSのように書けるのでしっくりきますね。
この記事ではSassの構文などをSCSS記法で紹介していきます。
ネスト
以下のコードのように同じセレクタを何度も書くことはないでしょうか。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | .header-nav { /* スタイル記述 */ } .header-nav ul { /* スタイル記述 */ } .header-nav li { /* スタイル記述 */ } .header-nav li a { /* スタイル記述 */ } |
毎回書くの手間だなと思われたことはないでしょうか。
そんなときSassだと以下のように記述できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .header-nav { /* スタイル記述 */ ul { /* スタイル記述 */ } li { /* スタイル記述 */ a { /* スタイル記述 */ } } } |
楽ですね!!!
同じセレクタを何度も記述する必要がなくなり、HTML構造が把握しやすくなりました。
クラスの変更があっても、一カ所変更したのでOKです。
ただ、以下のようにネストが深くなりすぎないように気を付けてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .home { .about-block { .about-list { > li { a { &::before {} } .title {} } } } } |
HTML構造に従って記述していくと、ネストがどんどん深くなってしまいます。
個人的には、3~4つネストするのが限度かと思います。
ネストの階層が深すぎると、どのスタイルが当たっているかコードを追うのが大変です。
使える場所も限定されてしまうので、コンポーネントとして流用したいときに使いづらくなります。
階層があまりに深くなるようでしたら、セレクタの指定を見直してみてください。
以下の点を疑ってみるといいと思います。
・HTML構造に完全に沿ってネストしていないか
・コンポーネントとして外に出せないか
筆者なら以下のように書きます。
これが正解というわけではありませんが、先ほどよりはコードが見やすくなったのではないでしょうか。
1 2 3 4 5 6 7 8 9 10 11 12 13 | .home {} .about-block {} .about-list { > li {} a { &::before {} } .title {} } |
子セレクタ・隣接セレクタ
CSSでは子セレクタ(>)・隣接セレクタ(+)を以下のように書きます。
1 2 3 4 | .list > li {} .box + .box {} |
Sassだと以下のように記述できます。
1 2 3 4 5 6 7 8 9 | .list { > li {} } .box { + .box {} } |
親のセレクタに関係あるスタイルがまとまって、スタイルの把握がしやすい気がします。
反対に記述が増えて見づらいと思った場合は、ネストせずにCSSの記述に戻しましょう。
メディアクエリ
レスポンシブ対応でメディアクエリを使用すると思います。
1 2 3 4 5 6 7 8 9 10 11 | p { font-size: 20px; } @media screen and (max-width: 768px) { p { font-size: 14px; } } |
指定するセレクタが増えていくとメディアクエリでの記述と通常の記述の位置が離れて、スタイルの把握しにくいときがあります。
そんなときSassだと以下のように書けます。
1 2 3 4 5 6 7 8 | p { font-size: 20px; @media screen and (max-width: 768px) { font-size: 14px; } } |
このコードをコンパイルすると先ほどのCSSが出力されます。
Sassだと上記のようにセレクタ内にメディアクエリを記述できるので、そのセレクタに対する全てのスタイルを把握しやすくなります。
親セレクタの参照
&(アンパサンド)で親セレクタを参照できます。
よく使う例としては疑似クラス・疑似要素・複数クラスではないでしょうか。
他にも親セレクタの前にセレクタを追加することもできます。
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 30 31 | .box { /* スタイル記述 */ &:hover { /* スタイル記述 */ } &::before { /* スタイル記述 */ } &::after { /* スタイル記述 */ } &.error { /* スタイル記述 */ } div & { background-color: #fff; } div:not(&) { opacity: 0.8; } } |
コンパイルすると以下のようになります。
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 | .box { /* スタイル記述 */ } .box:hover { /* スタイル記述 */ } .box::before { /* スタイル記述 */ } .box::after { /* スタイル記述 */ } .box.error { /* スタイル記述 */ } div .box { /* スタイル記述 */ } div:not(.box) { /* スタイル記述 */ } |
また、この記述はBEMというCSS設計だと威力を発揮します。
1 2 3 4 5 6 7 8 9 10 | .media { &__item { &--round { } } &__title {} &__content {} } |
BEMは「Block__Element–Modifier」という命名規則なので、ブロックが何度も出てきます。
それを&で参照することで、何度も書く手間を省くことができます。
ただこの方法は賛否両論あります。
クラス名「media__item」でSass内を検索してもヒットしないからです。
このような記述をすると、複数人で作業する場合は余計な混乱を生みかねないです。
・BEMでクラスをつけることが標準である
・BlockなどのコンポーネントごとにSCSSファイルを分割して記述するようになっている
など上記のようにルールが決まっている場合は使用しても問題ないと思いますが、そうでないならこの方法は見送ったほうがいいかもしれません。
プロパティのネスト
プロパティもネストできます。例えばfont-weight、font-family、font-size等は「font-」が共通ですよね。
これを以下のようにまとめることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | .title { font { size 14px; weight: bold; family: "游ゴシック Medium","Yu Gothic Medium"; } margin { bottom: 20px; top: 10px; } } |
現在ではEmmetが普及し、スタイルを書くのが楽になりました。
なのであまり使用するケースは少ないと思いましたが、紹介させていただきました。
Emmetについて知りたい場合はこちらの記事もどうぞ。
Emmet(エメット)でHTMLとCSSを効率的にコーディングする
コメント
1行のコメントは//コメントで、複数のコメントは/* コメント */で、記述できます。
1 2 3 4 5 6 7 | // このコメントはCSSで削除されます /* このコメントはCSSコメントとして、コンパイルされます */ /*! このコメントは圧縮モードでコンパイルされても、CSSに出力されます */ |
1行のコメントはCSSにコンパイルすると、出力されません。
複数行はCSSコメントとして出力されますが、圧縮モードでコンパイルした場合は出力されません。
圧縮モードでCSSコメントを表示したい場合は「/*」を「/*!」にしてください。
変数
よく使う値を変数として格納できます。
CSSにも変数はありますが、IEが対応していないのでSassの変数を使うことが多いです。
$変数名: 値;で変数を定義できます。
1 2 3 4 5 6 7 | $base-color: red; .title { color: $base-color; } |
コンパイルすると以下のコードのようになります。
1 2 3 4 5 | .title { color: red; } |
サイトのイメージカラーなど変数としてまとめておくと便利です。
ちなみにcal()内にSassの変数を使おうとすると、エラーになってしまいます。
このように変数が参照できないときは、以下のように#{$変数名}と記述すると展開されます。
これを補完(インターポレーション)と言います。
1 2 3 4 5 6 | $boxMargin: 25px; .box { width: calc((100% - #{boxMargin}) / 2); } |
モジュール
1つのCSSファイルで複数人が作業すると、競合が起きる可能性が高くなります。
またページ数が多いと、コード量が膨大に膨れ上がっていきます。
それを解決しようと複数のCSSファイルに分割すると、ファイルが増えた分リクエストが増えてしまいます。
①
1 2 3 4 5 | <style rel="stylesheet" href="base.css"></style> <style rel="stylesheet" href="module.css"></style> <style rel="stylesheet" href="home.css"></style> |
それを解決しようと、CSSの@importでファイルを読み込もうとするかもしれません。
②
1 2 3 | <style rel="stylesheet" href="style.css"></style> |
1 2 3 4 5 | @import url('base.css'); @import url('module.css'); @import url('home.css'); |
ですがこれは逆効果です。
①の場合は、複数のCSSファイルを同時に読み込んでいます。しかし@importを使った②の場合は、CSSを上から順番に読み込んでいきます。
ですので①の場合よりも読み込み時間が遅くなってしまいます。
Sassなら分割したファイルをモジュールとしてインポートしつつ、1つのCSSファイルにまとめて出力できます。
1 2 3 4 5 | @use 'base'; @use 'module'; @use 'home'; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | html { color: #000; background: #fff; font-size: 16px; line-height: 1.6; word-break: break-all; } body { background: #fff; margin: 0; } |
1 2 3 4 5 6 7 | .c-header { height: 140px; background-color: tomato; font-weight: bold; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .home { background-color: green; .btn { background-color: #333; color: #fff; transition: .3s ease-in-out; cursor: pointer; display: flex; align-items: center; height: 100px; } } |
これらのSCSSファイルをコンパイルすると、以下の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 26 27 28 29 30 31 32 33 34 35 36 | @charset "UTF-8"; html { color: #000; background: #fff; font-size: 16px; line-height: 1.6; word-break: break-all; } body { background: #fff; margin: 0; } .c-header { height: 140px; background-color: tomato; font-weight: bold; } .home { background-color: green; } .home .btn { background-color: #333; color: #fff; transition: .3s ease-in-out; cursor: pointer; display: flex; align-items: center; height: 100px; } |
ファイル名の先頭に_を付けるとそのSCSSファイルはCSSファイルとしてコンパイルされず、モジュールとして扱われます。
このように、コンパイル後に余計なCSSを生成しないようにする機能をパーシャルと言います。
Dart SassとLibSass
以前は@useの代わりに@importを使用していましたが、それが廃止になるということで一時期話題になりました。
それに伴いLibSassが非推奨になり、Dart Sassへの移行が勧められています。
これから新しくSassを始める方はDart Sassを使用するのが良いと思います。
スタイルの継承
あるクラスに別のクラスの全てのスタイルと、そのクラス独自のスタイルを指定したいときがあります。
そんなとき下記のコードのように書くことが多いです。
①特定のクラスを付与していたら、追加のスタイルを記述する
②ベースのクラスから派生したクラスに追加のスタイルを記述する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // ① .btn { background-color: black; color: white; &.red { background-color: red; } } // ② .btn { &--red { background-color: red; } } |
①の場合、派生のクラスがどんどん増えていくとHTMLが乱雑になったり、
②の場合、両方のクラス(今回だとbtnとredまたはbtnとbtn–red)どちらかをつけ忘れると表示が崩れる可能性があります。
そんなときは@extendを使用すると指定したセレクタのスタイルを継承するので、上記の問題を解決できます。
継承とは?と思われたかもしれませんが、コードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 | .btn { background-color: black; color: white; } .btn--red { @extend .btn; background-color: red; } |
すると以下のようなCSSが出力されます。
1 2 3 4 5 6 7 8 9 10 | .btn, .btn--red { background-color: black; color: white; } .btn--red { background-color: red; } |
これでクラスに「btn–red」を書くだけで、想定の通りのスタイルが当たります。
ただ@extendをむやみやたらに使用するのは推奨しません。
継承すると、グループとしてまとめて出力されます。
1 2 3 4 5 6 | .btn, .btn--red { background-color: black; color: white; } |
同じ記述があるからだけの理由で、@extendを使用していくとコードが恐ろしいことになります。
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 | .ttl { font-size: 24px; text-align: center; font-size: bold; @media screen and (max-width: 768px) { font-size: 14px; } span { font-size: 10px; } } .about-ttl { @extend .ttl; background-color: black; color: white; } .recruit-ttl { @extend .ttl; margin-top: 30px; } .message-ttl { @extend .ttl; color: blue; } |
すると以下のように出力されます。
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 30 | .ttl, .about-ttl, .recruit-ttl, .message-ttl { font-size: 24px; text-align: center; font-size: bold; } @media screen and (max-width: 768px) { .ttl, .about-ttl, .recruit-ttl, .message-ttl { font-size: 14px; } } .ttl span, .about-ttl span, .recruit-ttl span, .message-ttl span { font-size: 10px; } .about-ttl { background-color: black; color: white; } .recruit-ttl { margin-top: 30px; } .message-ttl { color: blue; } |
どんどんグループとしてまとめられてしまうので、CSSの詳細度が高まっていきます。
継承元の記述と違うので上書きしたくなっても、詳細度で高いため!importantを使うしかない、などとどんどんコードが汚くなる可能性があります。
基本的に同じコンポーネント内での使用すると影響が少なくていいと言われています。
@extendを使用できないセレクタ
@extendは以下のセレクタでは使用できないので、気を付けてください。
・子孫セレクタ(セレクタ セレクタ)
・子セレクタ(セレクタ > セレクタ)
・隣接セレクタ(セレクタ + セレクタ)
・間接セレクタ(セレクタ ~ セレクタ)
メディアクエリ内で@extendを使用するときは注意しなければいけません。
@mediaの外にあるセレクタを@extendすることはできません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | %btn { display: inline-block; position: relative; border: 1px solid #333; border-radius: 5px; font-size: 14px; font-weight: bold; margin-top: 0; } @media screen and (max-width: 768px) { a { @extend %btn; } } // または a { @media screen and (max-width: 768px) { @extend %btn; } } |
上記のようなコードを書くとエラーになります。
ですので、以下のようにメディアクエリ内に継承元セレクタを記述しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @media screen and (max-width: 768px) { %btn { display: inline-block; position: relative; border: 1px solid #333; border-radius: 5px; font-size: 14px; font-weight: bold; margin-top: 0; } a { @extend %btn; } } |
プレースホルダー
Sassにはプレースホルダーというセレクタが存在します。
セレクタの先頭に%を記述することで、そのセレクタのCSSが出力されなくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | %btn { display: inline-block; position: relative; border: 1px solid #333; border-radius: 5px; font-size: 14px; font-weight: bold; margin-top: 0; } .btn--gray { @extend %btn; background-color: gray; } .btn--back { @extend %btn; padding: 22px; width: 300px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | .btn--gray, .btn--back { display: inline-block; position: relative; border: 1px solid #333; border-radius: 5px; font-size: 14px; font-weight: bold; margin-top: 0; } .btn--gray { background-color: gray; } .btn--back { padding: 22px; width: 300px; } |
通常なら以下のように継承元のセレクタも出力されますが、プレースホルダーを使用しているため出力されませんでした。
1 2 3 4 5 6 7 8 9 10 11 | .btn, .btn--gray, .btn--back { display: inline-block; position: relative; border: 1px solid #333; border-radius: 5px; font-size: 14px; font-weight: bold; margin-top: 0; } |
コンポーネントの共通のスタイルをプレースホルダーで記述し、継承していくことでCSSの乱雑にすることを避けることができます。
ミックスイン
@mixinを使用するとスタイルシート全体で再利用できるスタイルを定義できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | @mixin 名前 { // スタイルの記述 } @mixin 名前(引数, 引数: デフォルト値) { // スタイルの記述 } .ttl { @include ミックスイン名; } |
通常引数がある場合、すべてに値を渡す必要があります。
しかし、引数のデフォルト値を定義しておくことで引数をオプションになります。
デフォルト値が設定したい場合は、引数の後に: デフォルト値と記述します。
SassScript式をデフォルト値としてとることができます。
@mixinと@extendの使い分け
@extendと@mixinどちらを使えばいいか分からないと思いましたか?
人によって違うと思いますが、筆者は以下のように使い分けると思います。
・@extendは同じコンポーネント内での使用する
・@mixinは引数が必要な場合、
同じコンポーネントではないが、何度も同じ処理が繰り返される場合に使用する
他にもどちらを使うか書いている記事があるので、読んでみてはいかがでしょうか。
@extendを使うべき時、@mixinを使うべき時
Sassのmixinとextendを概念で理解する。
条件分岐や繰り返し
Sassは他のプログラミング言語で頻出する条件分岐や繰り返しなどの制御ができます。
@if
以下のように記述することで、条件によってスタイルを変更することができます。
1 2 3 4 5 6 7 8 9 10 11 | @if 条件 { スタイル } @else if 条件 { スタイル } @else { スタイル } |
@ifは@mixinとの組み合わせがよく出てきます。以下の公式のドキュメントから引用しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @mixin avatar($size, $circle: false) { width: $size; height: $size; @if $circle { border-radius: $size / 2; } } .square-av { @include avatar(100px, $circle: false); } .circle-av { @include avatar(100px, $circle: true); } |
すると以下のように出力されます。
1 2 3 4 5 6 7 8 9 10 11 12 | .square-av { width: 100px; height: 100px; } .circle-av { width: 100px; height: 100px; border-radius: 50px; } |
.circle-avには「border-radius: 50px;」のスタイルが追加されています。
なぜかというと@mixinを呼び出すときの第二引数$circleがtrueで、@mixin内の@if文を評価したからです。
@each
配列の要素に対して、それぞれ処理を実行できます。
1 2 3 4 5 | @each $変数名 in 配列 { スタイル } |
1 2 3 4 5 6 7 8 9 10 11 | $icons: ("eye": "\f112", "start": "\f12e", "stop": "\f12f"); @each $name, $glyph in $icons { .icon-#{$name}::before { display: inline-block; font-family: "Icon Font"; content: $glyph; } } |
@for
開始値から終了値まで処理を繰り返します。
throughは終了値を含めて処理を実行するのに対して、toは終了値を含めません。
1 2 3 4 5 6 7 8 9 | @for $変数名 from 開始値 through 終了値 { スタイル } @for $変数名 from 開始値 to 終了値 { スタイル } |
1 2 3 4 5 6 7 | @for $i from 1 to 5 { .about-item0#{$i} { background-image: url(../img/about/bg-img0#{$i}); } } |
上記のコードをコンパイルすると、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | .about-item01 { background-image: url(../img/about/bg-img01); } .about-item02 { background-image: url(../img/about/bg-img02); } .about-item03 { background-image: url(../img/about/bg-img03); } .about-item04 { background-image: url(../img/about/bg-img04); } |
@while
条件を満たしている間、処理を繰り返します。
繰り返す条件を変更していかないと、無限ループになるので気を付けてください。
1 2 3 4 5 6 | @while 繰り返す条件 { スタイル 繰り返す条件の処理 } |
1 2 3 4 5 6 7 8 9 | $value: 0; @while $value < 100 { $value: $value + 20; .mt-#{$value} { margin-top: $value + px; } } |
上記のコードをコンパイルすると、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | .mt-20 { margin-top: 20px; } .mt-40 { margin-top: 40px; } .mt-60 { margin-top: 60px; } .mt-80 { margin-top: 80px; } .mt-100 { margin-top: 100px; } |
まとめ
プログラミングのようにスタイルを書くことができるので、工夫すれば様々なことができそうです!
他にも色々機能があるのですが、最初はネストから始めるといいと思います。
Sassにも慣れてきて、もっと楽に書けるのではないかと思ったときに他の機能を試してみてはいかがでしょうか。
参考
Sass: Syntactically Awesome Style Sheets
Web制作者のためのSassの教科書
Sass の @extend はどこがすごいのか
【Sass実践編】Sassの条件分岐と繰り返し処理とは?Sassでの制御構文の書き方を紹介!
【Sass】@at-rootと@extendの便利な使い方!BEM設計を崩さずに@extendする