Kekeの日記

エンジニア、読書なんでも

HTML、CSS設計方法!OOCSS、SMACSS、BEMについて

f:id:bobchan1915:20190227035311p:plain

「良いHTML、CSS」の基本ルール

CSSといえど、プログラミング言語であることは間違いなく、とりわけ「CSS」だからというものはありません。

どの言語においても「変更に耐えうるコード」が一つの鍵となってきます。 特にCSSは軟弱な言語であり、セレクタの書き方、およびHTMLのクラス属性の命名など要件を満たすには難しい部分はあると思います。

そのような部分を明文化して、備忘録として残せれば望外の喜びだと思い、本記事を執筆しようと思いました。

アジェンダ

以下のようなアジェンダになっています。

一般論的な話

以下の要件の時にコードを書くこと、または変更すること、削ることになります。

  • 要件の追加
  • バグの修正
  • 設計の改善
  • リソース利用の最適化

1. 要件の追加

HTML要素が追加されたときに限らず、レイアウトを現行のものから変更したいときなどに要件が発生します。

例えば「ハンバーガーメニューを右上にしたい」などといったものです。

コード自体は振る舞いを記述するものであり、たとえ要素の場所の変更でも要件が追加されたとも考えることができます。

2. バグの修正

要素の場所を変更したいということは、コードの振る舞いを変更したいのと同値です。

つまり、「バグである」と考えることができます。

3. 設計の改善

CSS自体の構造修正です。

例えば以下のようなHTMLに対して二つのCSSを書くことができます。

<div id="main-page">
      <div class="content-container">
             <p class="content"> This is a blog </p>
      </div>
</div>

これに対して

CSSは

div > div > p {
     color: red;
}

とも書けますし、

.content-container > content {
      color: red
}

とも書けます。

どちらが良い方法かは、この記事を読めばわかると思います。

しかしながら、前者から後者に書き換えをしても何も振る舞いは変わりません。

4. リソース利用の最適化

グラフィカルな演算では、GPUを使ったりするイメージがあるでしょう。

ブラウザの仕組みを少し解説すると

DOMとCSSOMをレンダリングしてレイアウトを決めてペインティングをする

という流れになっています。Paintingが大きなコストをしめるのは問題ではないですが、DOMの変更を伴わないGIFやcanva要素、そしてCSSアニメーションプロパティが関与してきます。

animation@keyframesによって簡単に使用することができて、非常に一般的ですが制御しきれないという過ちが多いのも事実です。したがって、コストを著しくあげることになるケースがあります。

もちろん、このようなケースではリソースが主眼点であり、振る舞いもコードの構造も変更されません。

HTML、CSSの設計のポイント

HTMLに依存したCSSをしないこと

f:id:bobchan1915:20190227035311p:plain

多くの場合、デザイン変更でもっとも重要なのは構造変更、つまりHTMLを書き直すシチュエーションが多いでしょう。

そのようなときにHTMLを変更すると同時にCSSも変更しなければならないのは、しんどいでしょう。 何か一つHTML要素を変更すると、それ以上に大幅にCSSを変更しなければなりません。

ある程度、HTMLとCSSの結合度の低いコードを書くことが大事です。

例えば以下のようなHTMLコードがあるとします。

<div>
    <h2>ブログです</h2>
     <div>
            <p>hogehoge</p>
      </div>
</div>

そのようなときに以下のようなCSSを書いたとしましょう。

div > h2 {
       border: 1px gray solid;
       font-size: 28px;
}

div > div > p {
       font-size: 12px;
}

すると、以下のよう変更があったときが耐えられません。

<div>
       <div>
            <h5>Subtitle</h5>
        <div>
         <div>
              <h2>Title</h2>
              <div>
                   <p>hogehoge</p>
              </div>
         </div>
</div>

そのようなときに

<div class='title-container'>
    <h2 class='title'>ブログです</h2>
     <div>
            <p class='content'>hogehoge</p>
      </div>
</div>

としてCSSはclass属性に結合を持つ

.title {
    ....
}

.title-container > content {
    ...
}

とすればCSS側は変更する必要がなくなります。まとめるとh2などタグでスタイル付けをするのは好ましくないでしょう

絶対値でほとんどの値を定義している

以下のようなケースを考えます。

<div class='product'>
      <h2 class='product-name'>はてなブログ</h2>
       <div class='product-details'>
            <p class='product-description'>いいブログがかけます</p>
       </div>
</div>

そして以下のように絶対値で何もかも定義したら、あとあと面倒です。

ダメCSSコード❌

.product-name {
       font-size: 18px;
       line-height: 27px;
}

.product-description {
       font-size:18px;
       line-height: 27px;
}

しかし、font-sizeを大きくするとline-heightも同時に変えなければなりません。そのようなときは

良いコード✅

.product {
       font-size: 18px;
       line-height: 1.5
}

.product-description {
       font-size:18px;
}

とすることができ、font-sizeを変更してもline-heightをいじる必要がありません。特にproduct-descriptionproductから継承されるため、指定すらする必要がありません。ここで1.5としているのは単位をつけると、その値そのままが継承されてしまうからです。

不要なセレクタが縛っている

f:id:bobchan1915:20190227040019p:plain

不要なセレクタがあると、いくらclassを定義して頑張った気になったとしてもHTMLに束縛をされます。

例えば

<div class='product'>
      <h2 class='product-name'>はてなブログ</h2>
       <div class='product-details'>
            <p class='product-description'>いいブログがかけます</p>
       </div>
</div>

に対して

h2.product-name {
    ...
}

とするとh2に対して束縛をされていることになります。

.product-name{
     ...
}

としても問題はなく、h3になるなどマークアップに変更点があったとしても機能します。

長いセレクタ

セレクタ>でトップレベルからわざわざ指定をすると、構造そのものに束縛を受けることになります。

<div class='product'>
      <h2 class='product-name'>はてなブログ</h2>
       <div class='product-details'>
            <p class='product-description'>いいブログがかけます</p>
       </div>
</div>

の場合に

.product .product-details p {
     ...
}

でなくて

.product p {
     ...
}

とする方が移植性が高く、変更に耐えられます。

設計のアイデア

いくつかHTMLとCSSを設計のポイントを解説していこうと思います。

0. 最初に知っておきたい現状

これからCSSのOOCSS、BEM、SMACSSの3つの設計パターンを解説していきますが、世界ではどのような位置付けなのでしょう。

Google Trendで見てみましょう。

f:id:bobchan1915:20190227042935p:plain

圧倒的にBEMがよく使われているのではないかと推測することができます。

1. OOCSSを実践する

OOCSS(Object Oriented CSS)とは、オブジェクト指向の考えをCSSに取り込み、変更に耐えられるCSSをデザインするのが目的とするCSSのデザインパターンです。

OOCSSの恩恵としては

  • スピード: CSSファイルを小さくできるのでブロックせずにレスポンスを素早く返せる
  • スケーラビリティ: class属性をうまく使い、変更に耐えうるコードをかける
  • 効率性: DRYルールで、効率よくコードを書くことができる
  • メンテナンスが簡単: HTML要素に変更があっても、完全にすべてCSSを書き換える必要がない
  • 可読性: とにかくCSSが読みやすい。

ここでいるオブジェクト(Object)とは、繰り返されるビジュアルパターンです。

ルールは二つあって

  • 構造と見た目の分離
  • コンテナとコンテンツの分離

です。

1.1 構造と見た目の分離

構造とは、いわゆるユーザーに見えない要素のサイズやポジションなどをいいます。

構造に関するCSSプロパティでいうとどのように配置するべきかを定義する以下のようなものであり

  • Height
  • Width
  • Margins
  • Padding
  • Overflow

そして見た目に関するCSSプロパティはどのように見えるかを定義する以下のようなものです。

  • Colors
  • Fonts
  • Shadow
  • Gradients

例えば以下のような3つの要素があったとします。

<div class="morning-message">Good morning</div>
<div class="afternoon-message">Hello</div>
<div class="night-message">Good Night</div>

可視化をすると以下の通りです。

f:id:bobchan1915:20190227042109p:plain

このようなHTML要素に対して、以下のようなCSSを定義しています。

.morning-message {
     width: 150px;
     height: 50px;
     background: yellow;
}

.afternoon-message {
     width: 150px;
     height: 50px;
     background: red;
}

.night-message {
     width: 150px;
    height: 50px;
    background: gray;
}

このような要素だと別々にそれぞれ定義するよりは共通するclass属性greetingなどを定義すると共通要素をつけられます。

f:id:bobchan1915:20190227042232p:plain

まるで共通部分をオブジェクトの特性としてまとめたような感じです。

<div class="message morning-message">Good morning</div>
<div class="message afternoon-message">Hello</div>
<div class="message night-message">Good Night</div>

コンテナとコンテンツを分離

二つ目の「コンテナとコンテンツの分離」は場所に依存しないCSSを書くということです。

一貫性と、メンテナンス性を提供します。

以下のように.sidebarのように「場所」に関するスタイルと、.listのように「コンテンツ」に関するCSSを分離することによって、どこにlistを配置してもスタイルが崩れることなく、移行することができます。

.sidebar {
    padding: 2px;
    left: 0;
    margin: 3px;
    position: absolute;
    width: 140px;
}

.list {
    margin: 3px;
}

.list-header {
    font-size: 16px;
    color: red
}

.list-body {
    font-size: 12px;
    color: #FFF;
    background-color: red;
}

SMACSSを実践する

SMACSS(スマックス)とはフロントエンドのスタイルガイドで、OOCSSを元に定義されています。

SMACSSはパターン化のルールを定義しているものです。

  • Module: Layoutを構成する単位。
  • State: エラーや隠れたりするなど状態が変わるようなものです。is-の接頭語をつけます。is-hiddenis-active-tableのように状態を定義します。
  • Theme: テーマを切り替えるなど包括的な機能を実現するものに対して使われます。ocean-hokorobiなどとテーマをbody要素などtheme-の接頭語をつけて設定します。

1. Base: 各要素のデフォルトスタイルを定義

Baseは、各要素のデフォルトを定義します。

例えば、以下のような指定方法があります。

html, body, form {
    margin: 0;
    padding: 0;
}

input [text=text] { border: 1px solid red;}
a { color: blue }
a:hover { color: green; P

2. Layout: ページをセクション毎に分割する構成単位のスタイル

慣習的にidを用いて定義をしていきます。

#header, #footer {
   ...
}

などと定義をします。しかし、gridなどは.l-gridのようにクラス属性を使ってスタイルを指定してきます。

idを指定しないときは.l-と接頭語をつけます。

3. Module: Layoutの構成単位

ほぼすべてのパターンがこのModuleになります。

接頭語はm-mod--をつけたりします。

4. State: エラーや隠れたりするなど、状態を示す

例えばis-hiddenのように、隠れた要素にclass属性を定義します。

例えば隠したい要素は

<div class="product">
    <div class="message soldout-message">
         ...
    </div>
    <div>
        ...
    </div>
</div>

のようにsoldout-messageのようなものをさします。

.message {
    ...
}


.soldout-message {
     color: red;
}

5. Theme: テーマルールを指定するもの

わかりやすく、テーマについてである。

例えばHTMLのBodyタグに以下のようにページ全体についてのスタイル付けをしていくものがある。

<body class="theme-red">
...
</body>

CSSは以下の通りである。

.theme-red #menu {
    backgroud: white;
}

.theme-red #navi-bar {
   ...
}

のようにします。このようにテーマに関する影響をうけるものはtheme-と接頭語をつけることが推奨されています。

このようにすることによって、ページ全体を書き換えることができる。

BEMを実践する

BEM(Block Element Modifier)とは、再利用可能なコンポーネットとコードを共通化するためのメソッドです。

開発者に人気の命名規則で、多くのマークアップフロントエンドで取り入れられているものです。

公式サイトは以下の通りです。

getbem.com

特徴として

  • 簡単: 命名規則を覚えるだけ
  • モジューラ: 独立したブロックとCSSセレクタを使う
  • 柔軟性: 簡単に設定できて、再定義できる

が挙げられます。それでは、詳しくみていきましょう。

Block

Blockとは、命名規則の基礎を作るもので、独立した要素を定義します。

例えば以下のような要素をさします。

  • button
  • text field
  • menu
  • heading

  • 命名規則は短く簡単に決めます。接頭語にb-をつけたりするケースもありますが、普通はつけません。

  • CSSセレクタはclass属性だけで、tagやidを使わず他の依存性は一切無くします。

つまり

<div class="block">
    ...
</div>

そしてスタイルは以下のようにします。

.block {
     color: red;
}

Element

f:id:bobchan1915:20190227050124p:plain

Blockを構成するものです。

これは以下のようなものをさします。

  • text fieldのlabel
  • headingのlogo
  • menuのitem

また、以下のようなルールがあります。

  • 命名規則は[BLOCK名__ELEMENT名]で命名します。
  • CSSセレクタは単独で指定します。つまり[BLOCK名__ELEMENT名]で指定します。

つまりHTML要素は以下のようにします。

<div class="block">
    <span class="block__element">...</span>
</div>

CSSは以下のコードにします。ここでは正しいコードと間違ったコードを示します。

✅良いコード

.block {
    ...
}

.block__element {
    ...
}

❌悪いコード

.block {
    ...
}

.block.block__element {
    ...
}

Modifier

f:id:bobchan1915:20190227050305p:plain

ModifierはBlockやElementのバリエーションを定義するものです。

  • 命名規則は[BLOCK名orElement名]--[Modifier名]でつけます
  • CSSセレクタは
    • .block--mod .block__elementでBlockレベルのModifierを定義し
    • .block__element--modでElementのModifierを定義します

これを実践すると以下のような構成になります。

<div class "block block-mod">
     <div class="blokc block--size-big>
          ...
</div>
.block--hidden {
    ...
}

.block--mod .block__element {
     ...
}

サンプル

例えば以下のようなフォームがあったとします。

<form class="form form--theme-xmas form--simple">
  <input class="form__input" type="text" />
  <input
    class="form__submit form__submit--disabled"
    type="submit" />
</form>

そして以下のようにCSSでスタイル付をしていきます。

.form { }
.form--theme-xmas { }
.form--simple { }
.form__input { }
.form__submit { }
.form__submit--disabled { }

まとめ

これで圧倒的にCSS設計力がついたのではないでしょうか。

ぜひ、みなさんもBEMから実践してみてください。

参考文献

css-tricks.com

www.keycdn.com