To end the Specificity Wars.

CSSの問題点

CSSはグローバルスコープ詳細度などCSSの仕様により、スタイル定義が複雑化しやすく、ファイルサイズも肥大化しやすい。

それらの問題を極力回避するため、CSS設計が必要となってくる。

CSS設計

CSSには破綻しにくい設計が求められる。

メジャーなCSS設計手法としては、 BEMSMACSSFLOCSS などがあるが、本記事では ITCSS をプロダクトに利用した際の所感を含めて記載をしていく。

ITCSS とは

ITCSSは「Inverted Triangle CSS (逆三角形のCSS)」の略称である。

ITCSSの構造
ITCSSの構造

CSS のスタイル定義を詳細度の広い順に記述(階層化)することを提唱している。その記述が逆三角形として視覚化されるため、この呼名となっている。

レイヤー

スタイルの機能を基本的には下記のレイヤーで分類する。

順番 レイヤー
1 Settings
2 Tools
3 Generic
4 Elements (Base)
5 Objects
6 Components
7 Trumps

詳細度の広い順に定義をしていくため、下のレイヤーに行くにつれ詳細度は上がっていく。

Settings

プリプロセッサなどで利用するグローバル変数や全体の設定を置く。

ここではプロジェクト内のグローバルな設定を記述する。(SASS変数やCSS Variablesの定義)

$font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default;

$white: #fff !default;
$black: #000 !default;
$gray: #f8f9fa !default;

Tools

プリプロセッサで利用するmixinfunctionなどの定義を置く。

@function str-replace($string, $search, $replace: "") {
  $index: str-index($string, $search);

  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
  }

  @return $string;
}

@mixin text-truncate(){
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}

Generic

リセットスタイル(normalize.css など)や固有のリセットスタイル定義を置く。

@import '~normalize.css/normalize.css';
*,
*::before,
*::after {
  box-sizing: border-box;
}

h1, h2, h3, h4, h5, h6 {
  margin-top: 0;
  margin-bottom: 0;
}

印刷用CSS(print.css)の定義ならここに置く。

Elements (Base)

ここではクラスセレクターなどは使用せず、要素セレクターで定義する。

a {
  color: #333;
  text-decoration: none;
}

Elements以下のレイヤーでは、要素セレクターは使用されない。要素レベルのスタイルを超えた実装はクラスを使用して実装する必要がある。

Objects

装飾を持たないレイアウトを定義する。(OOCSS)
また、プレフィックスに.o-が使われる。

.o-container {
  margin: 0 auto;
}

.o-layout {
  display: flex;
  align-items: center;
}

Components

サイト内のコンポーネント(UIパーツ)を定義する。
プレフィックスに.c-が使われる。

.c-alert {
  padding: 1rem 2rem;
  margin-bottom: 1rem;
  border: 1px solid #333;
  background: #eee;
}

Trumps

ヘルパー・ユーティリティ系のスタイルを定義する。

これまでのレイヤーを上書きするスタイルや明示的なスタイル定義(テキストの中央寄せ等)などスコープがもっとも狭い定義になる。

#main { margin: 1rem 1.5rem; }
.text-justify { text-align: justify !important; }
.text-nowrap { white-space: nowrap !important; }

IDセレクターや!importantを利用できるレイヤー。

構成

各レイヤーの定義をフラットに配置するよう提唱されている。

例:

@import "settings.global";
@import "settings.colors";

@import "tools.functions";
@import "tools.mixins";

@import "generic.box-sizing";
@import "generic.normalize";

@import "elements.headings";
@import "elements.links";

@import "objects.wrappers";
@import "objects.grid";

@import "components.site-nav";
@import "components.buttons";
@import "components.carousel";

@import "trumps.clearfix";
@import "trumps.utilities";
@import "trumps.ie8";

しかしながら、こうしたフラットな配置はファイル数が増えるにつれて見通しが悪くなる。そのため、レイヤー毎にディレクトリを切る運用が散見されているように思う。(参考:GitHub | Search – ITCSS


私の場合は下記のような形でディレクトリを切り各ファイルを配置している。

例:

style
 ├── Base
 │   ├── _global.scss
 │   └── _typography.scss
 ├── Components
 │   ├── _alert.scss
 │   ├── _breadcrumbs.scss
 │   ├── _button.scss
 │   └── _card.scss
 ├── Generic
 │   ├── _font.scss
 │   ├── _print.scss
 │   └── _reset.scss
 ├── Objects
 │   └── _layout.scss
 ├── Settings
 │   ├── _colors.scss
 │   └── _variables.scss
 ├── Tools
 │   ├── _animation.scss
 │   └── _mixins.scss
 └── Trumps
     └── _index.scss

ITCSSのメリット

プロダクトに ITCSS を取り入れた際に実感したメリット

柔軟性

ITCSSはフレームワークではなく、単なるガイドラインと言ってもいい。

「CSSのスタイル定義を詳細度順に記述する(詳細度を管理する)」という規則から逸脱していなければ、先述のようにレイヤーを追加や不要なレイヤーを削除することができる。
また、命名規則は特にないため、クラス命名もプロダクトに応じて自由に決めることができる。


たとえば、ITCSSには下記のレイヤーが欠けている。

  • ページ固有のスタイル
  • ライブラリ固有のスタイル

私のプロダクトでは、それぞれPagesVendorのレイヤーを追加して構築している。

順番 レイヤー
1 Settings
2 Tools
3 Generic
4 Elements (Base)
5 Objects
6 Components
7 Pages
8 Vendor
9 Trumps

これはITCSSをベースとしたCSSフレームワークも同様の手法を取っている事が多い。

管理コスト

「CSSのスタイル定義を詳細度順に記述する(詳細度を管理する)」というルールにより、スタイル適用状態の見通しが良くなった。

スタイルを詳細度順とカスケーディング順に記述することは、CSSが詳細度順に適応され、カスケーディングによって記述の後のものから適応される仕様に合致する。そのため、スタイルが適応される状態をコード上から想像しやすくなる。

「見出し(heading)」のスタイルを実装する場合、Elementsレイヤーにはh1h6を整えるようなスタイル定義を行い、見出し・小見出しの実装は、Componentレイヤーにクラスに対して実装を行う(必要がある)。一貫してこういったスタイル定義になるため、各レイヤーには役割を果たすために必要なスタイルだけが定義される形になる。

場合によっては書き分けることにより全体的なコード量は増してしまうのだが、定義自体は複雑にはならない。見通しの良いコードになるため、管理コストは非常に少なくなる。
実際、後からジョインしたメンバーへの教育コストは減った。また、「どこに何が定義されているのか」「どこに定義を追加すれば良いのか」など判断も容易であった(ようだ)。

パフォーマンス

ITCSSのルールに則れば、詳細度を効率的に管理でき、不本意な競合や不要なオーバーライドを少なくすることができる。それにより、拡張性と冗長性が向上し、ファイルサイズのムダが少なくなる。

実際、CSSが肥大化していた既存プロダクトのCSSをITCSSで再構築してみると、CSSのファイルサイズを15.5KBから10KBまで削減することができた。元々のCSSが悪すぎたのも理由にあるが、他の非ITCSSプロダクトでも平均で45%程度は削減できている。

CSSのファイルサイズが小さければ読み込み時間も少なくなり、不要なスタイル定義が無ければレンダリングのコストも少なくなり、パフォーマンスにも寄与する。CSSの圧縮アルゴリズムを使った最適化や単純なminify化より、根本的なパフォーマンス最適化につながる。

おわり

ITCSSはCSS設計とクラスの命名規則がないため、既存プロダクトに盛り込みやすい。例えば、BEMなど別のCSS設計との組み合わせをすることも容易である。
単純にITCSSの規則に則るだけで、これまで紹介したようなメリットを享受することができ、小規模・大規模問わずプロジェクトに拡張性の向上とメンテナンスしやすいCSSを構築することができると思う。

参考

記事

コードサンプル