非デザイン系エンジニア向けフロントエンド入門!CSSの肥大化と難読化を防ぐBEM規約とは?

0. はじめに

こんにちは!育良啓一郎です。サーバでPHP、フロントで Riot.js/Sass を書いているアプリケーションエンジニアです。私は基本的にはサーバサイドが主業務なのですが、最近はフロントも触る機会が多くなってきました。SPA化の流れを感じます。

非デザイン系のエンジニアでフロントサイドに不慣れな場合、下記のような事態に心当たりはありませんか?

  • どのクラスがどこに適用されているか、CSSを見てもわからない
  • 気がついたら似たようなスタイルを何度も書いていた
  • 全く関係ない場所のスタイルが原因でページが崩れた
  • スタイルを上書きしようと思ったら !important で既に上書きされていた

実はコレ、特に難しい技術を使わずとも簡単に命名規則を作るだけでかなり改善します。
今回はそんな命名規則の1つである「BEM規約」をご紹介します。

1. CSS設計思想あれこれ

CSSを書く時、こんな風に書いてはいないでしょうか。

<div id="header">

    <h1>イカしたタイトル</h1>

    <div class="cool-text">

        クールなテキスト

        クールなテキスト

    </div>

    <h2>イカしたサブタイトル</h2>

    <div class="cool-text">

        ホットな話題

    </div>

</div>

<div id="container">

    クールなテキスト

</div>
#header {
	width: 1000px;
	background: #def;
	padding: 10px;
}

#header h1,
#header h2 {
	color: #f00;
}

#header .cool-text {
	color: #33d;
	margin: 10px
}

.notice {
	color: #f00 !important;
}

#container {
	width: 1000px;
	background: #eee;
	padding: 10px;
}

#container p {
	color: #33d;
	margin: 10px
}

一見そこまで問題は無さそうですが、要素を追加したり同じスタイルを当てようとした時に問題が出てきます。

  • CSSを見ただけでは、何を意図したスタイルなのかわかりにくい。
  • セレクタにidやHTML要素を用いているため、スタイルの優先度が強く !important での上書きを乱用してしまう。
  • 特定の場所専用のスタイルになってしまい、似たようなスタイルを作る時にコピペを繰り返すことになる。

これらを解決するため、CSSには様々な設計思想が考えられてきました。

OOCSS(「Object Oriented CSS」)

オブジェクト指向をコーディングの世界に取り込んだ設計思想です。
ページを構成パーツ毎に分け、あたかもブロックのように組み合わせてコーディングしていく考え方です。

例えば、上記例はこんな風に書き換えられます。

<div class="box box-blue">

    <h1 class="title-red">イカしたタイトル</h1>

    <div class="text text-blue">

        クールなテキスト

        クールなテキスト

    </div>

    <h2 class="title-red">イカしたサブタイトル</h2>

    <div class="text text-red">

        ホットな話題

    </div>

</div>

<div class="box box-gray">

    <div class="text text-blue">

        クールなテキスト

    </div>

</div>
.box {
	width: 1000px;
	padding: 10px;
}

.box-blue {
	background: #def;
}

.box-gray {
	background: #eee;
}

.title-red {
	color: #f00;
}

.text {
	margin: 10px
}

.text-blue {
	color: #33d;
}

.text-red {
	color: #f00;
}

「.box」と「.box-gray」のように、構造と見た目を分離することでスタイル指定がわかりやすくなりました。
また「.header の h1要素」のように場所で指定するのではなく「.title-red」と独立したパーツとすることで、場所に縛られずに使うことができます。

SMACSS(「Scalable and Modular Architecture for CSS」)

CSSを設計する際、繰り返し使うパターンを「ベース/レイアウト/モジュール/ステート/テーマ」の5つに分類整理する考え方です。
例えば、reset.css のようなデフォルトルールは"ベース"に、グリッドのような要素配置のルールは"レイアウト"に、といった具合です。

これにより、ページごとに何度も同じようなスタイルを作り直す必要も、スタイルの上書き合戦も、一気に減らすことができます。
また必然的にCSSが分離されるため、1つのCSSファイルが肥大化して何処に何が書いてあるかわからなくなる、なんてことも防げます。

なるほどわからん

概念的にはなんとなくわかっても、いざ具体的にどう書いたら良いの?という段になると、それなりに学習が必要です。

単にOOCSSを使うだけでは、どこで使っているかわからない問題と、"クラス名被り"問題が解決できません。
また、SMACSSもこれはモジュール?レイアウト?と、初学者には分類がしづらい側面があります。

フロントは不慣れだけど、なるべく汎用的で長く使えるCSSを作りたい!

そんな時におすすめしたいのが、BEM規約です。

2. BEM規約

BEM(「Block-Element-Modifier」)は、ロシアの検索エンジンYandexを運営するYandex社により提唱された、CSS命名規則です。
公式ドキュメント: https://en.bem.info/methodology/

どんなものか、まずは具体例をお見せします。

<div class="contents-box">

    <h1 class="contents-box__title">イカしたタイトル</h1>

    <div class="contents-box__text">

        クールなテキスト

        クールなテキスト

    </div>

    <h2 class="contents-box__title">イカしたサブタイトル</h2>

    <div class="contents-box__text contents-box__text_color_red">

        ホットな話題

    </div>

</div>

<div class="contents-box contents-box_color_gray">

    <div class="contents-box__text">

        クールなテキスト

    </div>

</div>
.contents-box {
	width: 1000px;
	padding: 10px;
	background: #def;
}

.contents-box_color_gray {
	background: #eee;
}

.contents-box__title {
	color: #f00;
}

.contents-box__text {
	margin: 10px;
	color: #33d;
}

.contents-box__text_color_red {
	color: #f00;
}

アンダーバーとハイフンが入り混じって、何だこれは…… と思われるかもしれません。
これが、おすすめしたい「BEM」です。まだブラウザバックしないで!

慣れない内は、記法が独特で居心地の悪さや冗長さを感じますが、下記のような利点があります。

  • 特定の場所・文脈に依存しないため、再利用が簡単
  • クラスしか使わないため、優先度が複雑化しない
  • 名前が長い事で、"クラス名被り"を防ぎやすい
  • HTML/CSS上を見ただけでもクラス名の意図がわかりやすい
  • 命名規則がはっきりしているため、複数人で作業しても破綻しにくい

使い方を詳しく見ていきます。

3. BEM の使い方

BEMでは、その名の通り、クラス名を Block-Element-Modifier に分けて命名します。

Block ... ページに依存せず、独立して切り出せる構成要素。
Element ... Blockを構成する、そのBlock内のみで使うパーツ。
Modifier ... 色やサイズなど、見栄えや振る舞いが異なる状態を表す。

区切り文字

まず、それぞれの区切り文字としては下記を用います。
BEMの見た目が独特になる最たる所以ですね。

Block と Element ... __(アンダーバー2つ)
Modifier ... _(アンダーバー1つ)
長い名前の区切り文字 ... -(ハイフン1つ)

これにより「header-box__page-title_size_m」のようなクラス名になります。
プロジェクトによってこのあたりの規約をカスタマイズする場合もありますが、上記がデフォルトの区切り方です。

Block

Blockは、ヘッダーやメニュー、検索やログインのフォームなど、「ページを構成するかたまり」を指します。
「検索フォームをフッターに移動したいんだけど…」とか言われても移動できるよう、独立した要素として捉えます。名前空間みたいですね。

この「検索フォームをフッターに移動したいんだけど…」、経験ありませんか!私は、自分でやったコーディングに自分で苦しんだことがあります…
これがBEMでは、基本的にページの文脈に依存しないため、HTMLをそのままそっくり移動するだけで済むようになります。

また、Blockはネストする事も可能で、headerブロック内の検索フォームブロックの… のようなコーディングができます。
それぞれが独立しているため、ネストしたり外したりも容易です。

<div class="header">

    <img class="header__logo" />

    <!– ネストされたブロック –>
    <form class="search">
        <input class="search__input" type="text" />
        <button class="search__button"></button>
    </form>

</div>

<!– 他所でも使える –>
<form class="search">
    <input class="search__input" type="text" />
    <button class="search__button"></button>
</form>

Element

ヘッダー:ロゴ・タイトル、メニュー:各項目、フォーム:input要素・ボタンなど、「各かたまりのパーツ」のことです。
かたまりの外では使えず、かたまりに依存している物がElementに分類されます。

"赤い"とか"大きい"などの「状態」ではなく、"アイテム"や"テキスト"など「用途」で命名します。

また、Elementもネストが可能です。ネストする際はElement名を続けるのではなく、単にタグを入れ子にしていきます。

<div class="contents">

    <div class="contents__item-list">

        <!– ネストされたエレメント –>
        <div class="contents__item">…</div>

        <!– これはダメ –>
        <div class="contents__item-list__item">…</div>

    </div>

</div>

Modifier

選択状態のメニュー項目、押されたボタンなど、「ちょっとだけ見た目が変わる時」につける修飾子です。
命名は、"見た目"や"状態"、"振る舞い"などで決定していきます。

Modifierは、BlockとElementの、どちらにもつけることができます。

Modifierを付けたクラスは、HTML上で付けていないクラスと併用します(OOCSSのように)。単独では用いません。

class="box__text box__text_color_red"

また、大きく二種類あり、「赤い」「小さい」などの"値"を表すものと「無効化された」などの"真偽"を表す物があります。
それぞれ、「Key-value」、「Boolean」と呼ばれます。

「Key-value」

.title_size_m (「Mサイズ」のタイトルブロック)
.header__logo_theme_xmas (「クリスマス仕様」のロゴエレメント)

「Boolean」

.button_disabled (無効状態のボタン)
.menu__link_hoverd (ホバー選択状態のリンク)

この辺りは、他の記事を見ると「--(ハイフン2つ)」で区切るケースが多いようですが、
公式ドキュメントでは「_ (アンダーバー1つ)」がデフォルトになっているようです。

4. Sassとの相性

Sass(「Syntactically Awesome Stylesheets」)は、CSSの拡張言語の1つで、恐らく最も普及しているものです。

通常のCSSでは出来ない、セレクタをネストした書き方や、変数・計算式等も使用する事ができます。

例として、下記CSSを書くとします。

.main {
	width: 200px;
	border: 1px solid #333;
}
.main .inner {
	color: #333;
}
.main .inner_red {
	color: #f00;
}

上記のCSSは、Sassで次のようにまとめて書けます。入れ子構造が分かりやすくなっているのがわかるでしょうか?

$dark-gray: #333;

.main {
    width: 100px * 2;
    border: 1px solid $dark-gray;
    .inner {
        color: $dark-gray;
        &_red {
            color: #f00;
        }
    }
}

BEMは命名が名前空間のようになっている関係で、このSassのネストとの相性がバツグンに良いです。

たとえば、最初に書いた例は下記のように書き換えられます。

.contents-box {
    width: 1000px;
    padding: 10px;
    background: #def;

    &_color_gray {
        background: #eee;
    }

    &__title {
        color: #f00;
    }

    &__text {
        margin: 10px;
        color: #33d;

        &_color_red {
            color: #f00;
        }
    }
}

Blockを名前空間として整理できるので、長期間運用しても破綻しにくくなります。
もちろん、長期間運用していく中で似たようなBlockが量産されたり、単一のBlockにElementを詰め込まれる可能性はありますが、それでもある程度整理された状態は保たれるため、無策よりは全然マシな運用が出来ます。

記事によっては、ElementやModifierの指定の際に @extend 等を用いて、Modifier無しクラスのスタイルを継承させている場合もありますが、公式ドキュメント上では、ModifierはModifier無しクラスと併用する事になっているため、公式デフォルトのまま使うならば特に何もしなくてよいと思います。

ちなみに、BlockとElementがネスト可能と上で書きましたが、Sass上ではネストさせてはいけません。
Sassでネストさせると、子Block/Elementが、親Block/Elementに依存する事になってしまうため、規約違反になってしまいます。

5. まとめ

開発需要の高まりで、触れる機会が増えてきたフロントサイドですが、その開発手法は多様化し複雑な状況になっています。
そのなかでもBEM規約は、既に"枯れた技術"となりつつある感があり、資料も多く安定的に使える物の1つになってきました。

フロントサイドを専門としていないエンジニアでも、CSSはちょっとした工夫で汎用的かつ破綻しにくい物にすることができます。
もしまだ無策CSSを書かれていたら、まずは是非、BEMを使ってみてください。