こんにちは。Eight 事業部エンジニアの鳥山(@pvcresin)です。
業務では Eight Career Design の Web フロントエンドを担当しています。
Eight では様々な技術を使って、CSS によるスタイリングを行っています。
- PC 版 Eight
- Sass(SCSS) + BEM
- Eight-UI: 社内向け React コンポーネントライブラリ
- CSS Modules + PostCSS
- Eight のとある新規開発
- styled-components
今回は、CSS にまつわる技術を復習し、Web におけるより良いスタイリングについて考えていきたいと思います。
CSS
CSS は文書のスタイルを定義することができる、スタイルシート言語です。
ここで言う文書とは、HTML をはじめとするマークアップ言語によって書かれた、構造化された文書のことを指します。
h1 { color: blue; }
セレクタ(h1
の部分)によってスタイルを適用する要素を指定し、{}
ブロック内に実際のスタイルの定義を書いていきます。
Web サービスでは様々なページで統一的なスタイルを提供することが多く、文書(HTML)とスタイル定義(CSS)が分かれていることで、効率的にスタイルのメンテナンスを行うことができます。
CSS の設計思想
CSS で開発を進めていくうちに、CSS をどのように設計していくと開発しやすいか、といった知見が溜まっていきました。
そこで生まれたのが様々な CSS の設計思想です。
有名な設計思想として、
- OOCSS
- BEM
- SMACSS
- SUIT CSS
- FLOCSS
- RSCSS
- ITCSS
- MCSS
などが挙げられます。
今回はその中でも OOCSS、BEM をピックアップしたいと思います。
OOCSS
OOCSS(Object Oriented CSS)は、CSS にオブジェクト指向を取り入れた考え方です。
.container > .content
といった入れ子構造になっているセレクタをそれぞれの要素(.container
と.content
)に分けたり、レイアウトと見た目の定義を分けたりすることで、再利用性を高めました。
OOCSS は CSS の定義を細かく分解し、複数のクラスを組み合わせることによるスタイリングを提唱しました。
再利用性が高いということは、同時に、スタイルの影響範囲が広いことを意味します。
CSS は基本的にグローバルスコープであるため、意図しない要素にスタイルが適用される場合があり、変更時には注意する必要があります。
BEM
BEM(Block Element Modifier)は先の問題に対し、命名規則を工夫するというアプローチをとりました。
- Block
- 元となる要素
- Element
- Block 内の子要素
- Modifier
- Block や Element の変化した状態を表すもの
各要素とその状態を Block、Element、Modifier に分けて考え、block__element--modifier
という命名規則によって CSS を組み立てていきます。
例えば、
<ul class="list"> <li class="list__item"></li> <li class="list__item"></li> <li class="list__item list__item--active"></li> </ul>
のようなリストの場合、
.list { } .list__item { } .list__item--active { }
このような CSS になります。
OOCSS は、クラスの再利用を積極的に行うことによってコード量が少なくなる一方で、変更時の影響範囲は大きくなります。
対して BEM は、コード量が多くなる一方で、変更時の影響範囲が小さくすることができます。
Pure な CSS で BEM を使うと記述量が多くなり、負担が大きいので、CSS プリプロセッサとの組み合わせが一般的です。
CSS プリプロセッサ
これまで設計思想という運用によって開発のしやすさを担保していましたが、ツールによってこれを一段引き上げようという動きが出てきました。
CSS プリプロセッサは独自のメタ言語で書かれたスタイル構文を CSS 構文へと変換(コンパイル)してくれるものです。 1
代表的なものに
などが挙げられます。
ここでは Eight でも使用している Sass について取り上げます。
Sass
Sass(Syntactically Awesome StyleSheet)は CSS にセレクタのネストや変数、mixin、import などの機能を取り入れました。
記法として、インデントのみでネストしていく SASS 記法と、
div margin: 0 auto p padding: 20px
ブロックでネストしていく SCSS 記法があります。
div { margin: 0 auto; p { padding: 20px; } }
Eight では SCSS 記法を採用しています。
コンパイル後はどちらも、
div { margin: 0 auto; } div p { padding: 20px; }
のようになります。
かつては、gulp などのビルドツールにおいて変換することが多かった Sass ですが、
近年では webpack などのモジュールバンドラー内部で変換を行うことが多いと思います。
Eight における Sass
Eight では React 導入後、Sass と BEM を利用した、変更時の影響範囲が小さいスタイリングを意識して開発を行ってきました。2
大まかなイメージとしては以下のようなものです。
import bemClassName from "bem-classname"; const getClassName = (...args) => bemClassName("components-list", ...args); const List = () => ( <ul class={getClassName()}> <li class={getClassName("item", { active: false })}></li> <li class={getClassName("item", { active: false })}></li> <li class={getClassName("item", { active: true })}></li> </ul> );
BEM のクラス名を手軽に付与できる関数を作成し、コンポーネントの状態によってクラス名を出し分けます。
.components-list { &__item { &--active { } } }
.scss ファイルの一番外側のクラス(.components-list
)で名前空間を担保し、スタイルの影響範囲をコンポーネント内に閉じるようにしています。
名前空間の担保自体は Vue などに見られる Scoped CSS でも解決することができます。
しかし、Scoped CSS はコンポーネントがネストした場合、親要素のスタイル定義が子要素にも伝搬されるため、ターゲットの要素のみに確実にスタイルを適用する必要があります。
その場合においても BEM は有効な手段であると言えます。
CSS ポストプロセッサ
pre
(前)があれば、post
(後)があります。
ポストプロセッサは CSS をより良い CSS へと加工してくれるものです。
代表的なものに PostCSS があります。
PostCSS
PostCSS は CSS の加工ツールです。
PostCSS 上で動く様々なプラグインを入れることで、開発を支援してくれます。
有名なプラグインとして、
- Autoprefixer
- ベンダープリフィックスを付与する
- cssnano
- CSS を minify する
- PostCSS Sorting
- プロパティ宣言の順序をソートする
などが挙げられます。
PostCSS は便利なツールである一方で、とても自由度が高く、プラグインによってはオリジナルのメタ言語のような書き方になってしまう場合もあります。
Sass では仕様が一つに決まっていますが、PostCSS ではプラグインの組み合わせで書き方は無限大であるため、慎重に利用するプラグインを選定する必要があります。
CSS Modules
JavaScript(以下、JS)によって User Interface(以下、UI)を構築することが増えていく中で、 JS と CSS を紐づけるという考え方が生まれました。
CSS Modules は BEM が行ってくれていた名前空間の担保を自動で行ってくれる仕組みです。
対応する CSS に書いてあるクラスを JS で import して、コンポーネントとスタイルを紐付けます。
BEM では
const List = () => ( <ul class={getClassName()}> <li class={getClassName("item", { active: false })}></li> <li class={getClassName("item", { active: false })}></li> <li class={getClassName("item", { active: true })}></li> </ul> );
.components-list { &__item { &--active { } } }
だったのに対し、CSS Modules では
import styles from "list.css"; const List = () => ( <ul class={styles.list}> <li class={styles.item}></li> <li class={styles.item}></li> <li class={styles.itemActive}></li> </ul> );
.list { } .item { } .itemActive { /* .item を継承する */ composes: item; }
という風に CSS クラス名と JS とのマッピングを行います。
.itemActive
で使用しているcomposes
は上の.item
のスタイルを継承することを意味します。
実際にレンダリングされた時の class 名は.list
などではなく、.components-list__list__abc36
のようにファイル名やクラス名、ハッシュ値などを組み合わせて名前空間を担保してくれます。
CSS Modules は、CSS ファイルにスタイルを定義していくので stylelint などの Lint ツールのサポートがある程度期待できます。3
一方、CSS Modules が行ってくれるのはクラス名のマッピングに他ならないので、JS から CSS に変数を渡すことができないという問題があります。
Eight-UI では JS から CSS に変数を渡すことが少ないと考え、メリットとデメリットを比較した結果、CSS Modules を採用しました。
styled-components
JS で UI を構築しているなら、スタイル定義も JS で書いてしまおう、といった考え方もあります。
styled-components は JS でスタイルを定義する、いわゆる CSS in JS の一種です。
有名な類似ライブラリとして emotion が挙げられます。
JS の ES6 で追加されたタグ付きテンプレート文字列を利用しており、Pure な CSS に近い記法で書くことができます。
import styled from "styled-components"; const Book = () => ( <div> <Title /> <Author /> </div> ); const Title = styled.h2` color: red; `; const Author = styled.p` color: blue; `;
styled-components はスタイルではなくコンポーネントを返すため、スタイルを当てるためにクラス名とマッピングする必要がなくなりました。
CSS Modules はビルド周りの設定を行う必要がありますが、styled components は JS 内でライブラリを import するだけで使えるため、導入が容易であるといったメリットがあります。
そして、もちろん JS から CSS に変数を渡すことができます。
Eight のとある新規開発では、導入の容易性に加え、一つのファイルにコンポーネントとスタイルの定義を収めることができる点でメリットが大きいと考え、styled-components を採用しました。
最後に
CSS の歴史は人類の試行錯誤の歴史でもあります。
スタイルの再利用性を担保しつつ影響範囲を小さくするため、設計思想によって運用でカバーしていた時代から、いくつかのツールを組み合わせることで楽に解決できる時代になりました。
しかし、時代が変わっても、重要なことは大きく変わっていないと感じました。
それは、名前空間の担保や、関心の分離をしっかりと意識していくことではないかと思います。
これはエンジニアリング全般でよく言われていることですが、スタイリングにおいてもそれらが重要であることが再認識できました。
これからもより良いスタイリングを求めて精進していきます💪