今回は何かと敬遠されがちなSassの@extendと、忘れられてそうで案外そうでもない@at-rootの便利な使い方を紹介します。
本記事は以下のような人を対象に書いています。
- @extendの使い方はわかるけど、具体的にどんな場面で使えばいいかわからない
- @extendの何が便利なのか具体的にわからない
- @extendが何かと敬遠される理由がわからない
- @at-rootの使い方はわかるけど、使う場面がわからない
「技術書とかネットで知ったけど、具体的にどう使えばいいのかわからない…」という人にぜひ読んで欲しい記事です!
まずはそれぞれどんな機能なのか解説します。知ってる人は飛ばしちゃってください。
「早く便利な使い方を教えろ」という人はスキップ。
@extendの基本的な使い方
@extendはスタイルの継承ができるという機能です。
使い方は簡単で、スタイルを継承したいクラスを指定するだけです。
//scss
.block {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
.another-block {
@extend .block;
}
これをコンパイルすると以下のようになります。
//css
.block, .another-block {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
@extendしたクラスは継承元のクラスとまとめてスタイルが指定されます。
このように、指定したクラスのスタイルを別のクラスにそのまま継承できるのが@extendです。クラス以外にもidセレクタ(#block
)や要素セレクタ(div
)などでも使えます。
@extendのプレースホルダーを使う
@extendには専用のプレースホルダーがあり、これを使うと継承元のセレクタを生成せずにスタイルのみ継承できます。つまり余計なコードを減らせるということですね。
@extendのプレースホルダーを使うには先頭に%
をつけます。
//scss
%block-base { // ←@extendのプレースホルダー
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
.another-block {
@extend %block-base;
}
これをコンパイルすると以下のようになります。
//css
.another-block {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
%block-base
はコンパイル後はセレクタは生成されず、.another-block
にスタイルのみ継承できているのがわかります。
プレースホルダーを使うと余計なコードを生成しない上、「このスタイルは@extendされる」ということがわかりやすいので便利ですね。
@extendが使えない場面
複数のパターンを合わせるセレクタ
@extendは子孫セレクタや子どもセレクタなど、隣接するセレクタでは使えません。
//以下のコードはエラー
.block .item { // ←子孫セレクタ
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
.another-block {
@extend .block .item; // ←子孫セレクタからは@extendできない!
}
メディアクエリ内
メディアクエリの外に書いたスタイルをメディアクエリ内で@extendはできないので注意しましょう。
//以下のコードはエラー
.block {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
@media screen and (max-width: 768px) {
.another-block {
@extend .block; // ←外のスタイルはメディアクエリ内では@extendできない!
}
}
ただ、メディアクエリ内で@extendは使えませんが、@extend内でメディアクエリは使えます。その際は@extendするとメディアクエリ内のスタイルも継承されます。
//以下のコードはセーフ
.block {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
//@extendされるクラス内にメディアクエリは大丈夫
@media screen and (max-width: 768px) {
padding: 10px;
}
}
.another-block {
@extend .block;
}
@extendを使う際の注意点
@extendしすぎない
@extendは使いすぎるとコンパイル後のコードがカオスになるので注意が必要です。
//scss
%block {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
.block1 {
@extend %block;
}
.block2 {
@extend %block;
}
// これを10回繰り返してみる
同じスタイルの@extendを10回繰り返すとコンパイル後はこんな感じになります。
//css
.block1, .block2, .block3, .block4, .block5, .block6, .block7, .block8, .block9, .block10 {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
大量のセレクタが生成されてかなり見づらいです。上記はまだマシですが、30回@extendしたりクラス名が長かったりしたらもう手がつけられません。
@extendはやりすぎるとデバッグ時に精神が崩壊するのでやりすぎないようにしましょう。
@extend内でネストしない
@extend内でセレクタをネストして使うのもよくないです。汎用性がガクッと下がる上、コンパイル後のコードが非常にカオスになります。
//scss
%block { // ←プレースホルダー
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
.element { // ←@extend内でネスト
display: flex;
color: #fff;
}
.block1 {
@extend %block;
}
.block2 {
@extend %block;
}
// これを10回繰り返してみる
コンパイル後のコードはこんな感じ。
//css
.block1 .element, .block2 .element, .block3 .element, .block4 .element, .block5 .element, .block6 .element, .block7 .element, .block8 .element, .block9 .element, .block10 .element {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
クソ見づらいし、下に.element
というクラスを持つ要素じゃないと@extendできません(できるにはできますが、余計なコードが生成されるので思わぬバグの原因になりかねない)
下に特定のクラスを持つ要素じゃないと@extendが使えないなんて不便なのでやめましょう。
@extendの連鎖をしない
1回だけの@extendなら便利ですが、2回以上を連鎖させるとコードを追うのが途端に面倒になります。@extend内のスタイルを変えることでどのクラスの何がどう変わるのかが急に把握できなくなりますからね。タブーですよこれは…。
//scss
.block1 {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
.block2 {
@extend %block1; // ←@extend1連鎖目
background-color: pink;
font-size: 20px;
}
.block3 {
@extend %block2; // ←@extend2連鎖目
}
コンパイル後のコードはこんな感じ。
.block1, block2, block3 {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
.block2, .block3 {
background-color: pink;
font-size: 20px;
}
どこを変更したらどこに影響が出るのかが極めてわかりづらいので本当にやめましょう。
@extendが敬遠される理由
@extendが敬遠される理由は「正直使いづらいし、使い方をちゃんと考えないとコードが一気に見づらくなるから」なんですよね。満場一致で@mixinの方が使いやすい。
上に解説しただけでも、
- 複数パターンのセレクタでは使えない
- メディアクエリ内では使えない
- @extendしすぎてはいけない
- @extend内でネストしてはいけない
- @extendを連鎖させてはいけない
という感じでとにかく使う場面が限られてる上に使いづらい。
そんな@extendですが、ちゃんと使い方を考えれば結構便利に使えます。@at-rootと合わせて使うと便利なのですが、先に@at-rootの解説をしちゃいます。
@at-rootの基本的な使い方
@at-rootはネストされたセレクタをルートに戻せる機能です。
使い方はこんな感じ。
//scss
.block {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
//ルートに戻す
@at-root .element {
background-color: #fff;
font-size: 20px;
}
// 以下のような書き方でもok
@at-root {
.element {
background-color: #fff;
font-size: 20px;
}
}
}
コンパイル後はこんな感じ。
//css
.block {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #333;
}
//.blockにネストされていたが、ルートに戻っている
.element {
background-color: #fff;
font-size: 20px;
}
.block .element
とネストしていたクラスがルートに書き出されました。
@at-rootの機能はこれだけなので「いつ使うんだよ!」ってなりますよね。
このように@extendも@at-rootも「具体的な使い道がわからない…」という感じですが、この2つを合わせると便利な使い方ができます。
@extendの便利な使い方
というわけで本編です。
@extendの具体的な使い方として「同じようなスタイルのコンポーネントを作るときに、共通のスタイルを1つにまとめて、それを@extendする」というのが便利です。
例えば、以下の3種類のボタンが必要だとします。
色以外のスタイルが全て同じなので、コンポーネントの共通のスタイルを1つにまとめて@extendします。
共通のスタイルをプレースホルダーで指定し、共通じゃないスタイル(色)だけ個別に指定します。
イメージとしてはこんな感じ。
コードにするとこんな感じ。
//コンポーネントの共通スタイルをプレースホルダーで作成
%btn-base {
padding: 15px 40px;
color: #fff;
border-radius: 10px;
}
.btn-red {
@extend %btn-base;//継承
background-color: #fb3829;//赤
}
.btn-blue {
@extend %btn-base;//継承
background-color: #3381e1;//青
}
.btn-green {
@extend %btn-base;//継承
background-color: #33e154;//緑
}
一度共通のスタイルをプレースホルダーに指定してしまえば、あとは@extendするだけで同じコンポーネントが作れます。そこから独自のスタイルを追加して色などを変えるという使い方が便利ですね。
なお、クラスから@extendせずにプレースホルダーを使った理由は、プレースホルダーを使えば「どこかで@extendされてる」と一目で判断できるからです。
//これだけだと@extendされているか判断できない
.btn-base {
//スタイル
}
//プレースホルダーを使えばどこかで@extendされてると判断できる
%btn-base {
//スタイル
}
@extendするならクラスではなくプレースホルダーを使った方がわかりやすいので積極的に使いましょう。
また、このようにプレースホルダーを作っておくと別パターンのコンポーネントが必要になっても簡単に作れるというメリットがあります。
黄色のボタンを作りたいと思ったら、プレースホルダーを@extendして黄色のスタイルを指定すればもうできちゃいます。
ボタンのスタイルを変えたい場合もプレースホルダーのスタイルだけを直せば@extendしたクラス全てに継承されるので、1箇所修正するだけで全部自動で変わってくれます。
その結果修正しやすく保守性も高い、さらに見通しもいい綺麗なコードが書けます。これが@extendの使い方です。
また、前述の通り@extend内でメディアクエリのスタイルも指定できるので、レスポンシブ時のスタイルが全部一緒なら@extend内に書いちゃうのもいいですね。
%btn-base {
padding: 15px 40px;
color: #fff;
border-radius: 10px;
//プレースホルダー内でメディアクエリのスタイルを指定
@media screen and (max-width: 768px) {
padding: 10px 20px;
}
}
.btn-red {
@extend %btn-base;//メディアクエリ内のスタイルも継承される
background-color: #fb3829;
}
@extendしたクラスがネストされてしまう
しかし問題があります。BEM設計で@extendを使うとクラスがネストされてしまう問題です。
//scss
.block {
%btn-base {//プレースホルダーを作成
padding: 15px 40px;
color: #fff;
border-radius: 10px;
}
&__btn-red {
@extend %btn-base;//継承
background-color: #fb3829;
}
}
これをコンパイルすると以下のようになります。
//css
//↓クラスがネストされてしまう
.block .block__btn-red {
padding: 15px 40px;
color: #fff;
border-radius: 10px;
}
.block .block__btn-red {
background-color: #fb3829;
}
クラスがネストされると詳細度も変わってくるし、せっかくBEMの設計が台無しです。
@at-rootでネストを解除する
そこで@at-rootの出番です。ネストされたクラスをルートに戻すためにプレースホルダーを@at-rootで囲いましょう。
//scss
.block {
//プレースホルダーを作成し、ルートに戻す
@at-root %btn-base {
padding: 15px 40px;
color: #fff;
border-radius: 10px;
}
//以下の書き方でもok
@at-root {
%btn-base {
padding: 15px 40px;
color: #fff;
border-radius: 10px;
}
}
&__btn-red {
@extend %btn-base;//継承
background-color: #fb3829;
}
}
これをコンパイルすると以下のようになります。
//css
//↓ルートに出され、ネストが解除される!@extendもできてる!
.block__btn-red {
padding: 15px 40px;
color: #fff;
border-radius: 10px;
}
.block__btn-red {
background-color: #fb3829;
}
プレースホルダーを@at-rootで囲ったことにより、コンパイル後のCSSはルートに戻ってネストが解除されています。
これでBEM設計でもクラスをネストせずに@extendできました!
Sassを体系的に学習したい人は…
「Sassの使い方はなんとなく知ってるけどちゃんと学習してみたい!」という人に超オススメしたいのがWeb制作者のためのSassの教科書です。
Sassの機能が体系的に網羅されているだけでなく、環境構築や便利なパッケージ、ファイル管理など実践的な内容が詰め込まれています。
今までネットで情報を集めてなんとなく使っていた人は、一度本で体系的に学習すると一気にスキルアップできるのは間違い無いでしょう。
Sassの学習本の中ではダントツで内容が充実しているのでこれ1冊でSassは完璧ですね。
まとめ
まとめると「BEM設計で@extendを使うときは@at-rootで囲むとクラスをネストせずに出力できるよ!」というお話でした。
結構な人が@extendは使いづらく、@at-rootは使い道がないみたいな扱いをしていますが、この2つを合わせて使えば@mixinにも負けないくらい強力で便利です。
うまく使いこなせれば保守性もかなり上がると思うので、ぜひ試してみてください!