12周年記念パーティ開催! 2024/5/10(金) 19:00

知っておきたいCSS設計法 第1回 OOCSSの基本

HTMLテンプレートを設計する上で、基礎と考えられる概念のひとつOOCSSを取り上げます。オプジェクト指向のCSSとは何か? 今回はその考え方の基本をまずおさえましょう。

発行

著者 高津戸 壮 テクニカルディレクター
知っておきたいCSS設計法 シリーズの記事一覧

はじめに

このシリーズでは、OOCSS(オーオーシーエスエス)、BEM(ベム)、SMACSS(スマックス)という3つのCSS設計概念の概要を解説します。HTMLとCSSをどのように書いていけばよいかは、過去に「CSSの設計」シリーズにまとめました。今回のシリーズは、その2014年度版とでもいうような内容になっています。

というのも、CSSの設計を書いた頃と比較すると、さまざまな設計概念が広く知られるようになったものと思います。今回紹介するBEMやSMACSSが、その代表です。また、CSSのプリプロセッサSassのバージョンアップでは、BEMを意識したような機能なども追加されています。メンテナンスに強い堅牢なHTMLテンプレートを作るために、どのような考えを持っていればよいのか、OOCSS、BEM、SMACSSという3つのCSS設計概念を通し、考えていきます。

初出

この文章は『CSS Nite LP32 Sass』にて、高津戸が行った講演『HTMLテンプレートの設計』を元に、大幅に加筆した内容になっています。

はじめに

このシリーズの元となった講演、CSS Nite LP32 Sassは、Sassにフォーカスした内容です。しかし、筆者のセッションは、Sassの使用を前提としない内容としました。それは、Sassの使い方よりも前に、CSSをどうやって書いたらいいのか、きちんと考える方が先決だという思いがあったためです。

筆者は、仕事でHTMLとCSSを書くことが多いです。筆者が行っている業務は、受託案件が中心で、サイトは筆者の手を離れて、運用されることが多々あります。筆者個人としては、どの案件においてもSassを導入したいと考えてはいますが、そのような状態では、第三者である運用者が、サイトを運用していけることが重要。ですから運用者の開発環境を考慮し、Sassを導入しないという選択をすることも多いのです。

よく「一度Sassを導入したら、Sassなしの開発なんて面倒でやってられない」という話を聞くことがあります。その気持はよくわかります。しかし、Sassを使える場合でも、使えない場合でも、基本的には、実装したい設計の表現方法が異なるだけという捉え方を、筆者はしています。

もちろん、Sassには、CSSにはない、種々のプログラミング的な機能が実装されています。それらを用いれば、当然便利に書くことができますし、効率も上がります。同じコードをコピー&ペーストしなければいけなかったような場面でも、Sassの機能を使い、よりスマートに書くことができます。

しかし、HTMLとCSSを書いていく上で、もっとも時間がかかり、開発者を悩ませるのは、「どのようにHTMLとCSSを組み合わせてコードの設計を行うか」という点ではないでしょうか。

Sassは強力なツールです。しかしSassが「あなたがどのように設計すべきか」を教えてくれるわけではありません。Sassの力を真に生かすために、HTMLとCSSをどのように書くかという考えを固めましょう。世の中に知られているCSS設計論を眺めてみれば、ヒントが見つかるかもしれません。

OOCSSの基本

まずは、OOCSSについて解説します。OOCSSについてのWeb上のオフィシャルなドキュメントとしては、以下が挙げられるかと思います。

OOCSSについて正確な理解を得たいと考え、上記内容を読んだとしても、どうにも、掴みどころのないふわっとした印象を受けるかもしれません。実際のところOOCSSは、設計の考え方、概念が中心の内容であり、具体的な実装の方法論は、そこまで細かく解説されているわけではない——という捉え方をしておくぐらいでよいのではないかと、筆者は考えています。

しかしながら、OOCSSは、世の中に存在する種々のCSSの書き方の思想に影響を与えていると捉えて間違いありません。OOCSSはどのような考え方なのか——を知ることは、HTMLとCSSを設計していく上で、非常に重要であると筆者は考えます。

OOCSSとは

OOCSSとは、Object Oriented CSSの略です。これは、Nicole Sullivanという人が提唱した考え方です。

「Object Oriented」は、「オブジェクト指向」を意味します。これは、プログラムでよく使われる設計の考え方です。Nicole Sullivanは、ページを構成するコードを整理するのに、この「オブジェクト指向」の考え方を応用しました*。それは、どのような考え方なのかを見ていきましょう。

*注:OOCSSの「オブジェクト指向」

ちなみに、OOCSSが言う「オブジェクト指向」は「オブジェクト指向」の考え方のうちのごく一部にすぎないので、その名前は適切ではないという批判もあります。

OOCSSが考えるアンチパターン

まず、OOCSSが考えるアンチパターンを見てみましょう。例えば、以下のようなページがあったとします。

このようなページがあった場合、それぞれの見出しh2について、どのようにスタイルを適用するか考えてみましょう。例えば以下のように考えて、セレクタを決めるかもしれません。

  • メインエリアにある見出しは、#mainh2
  • 問い合わせブロックにある見出しは、.contactの中にある、.header以下にあるh2
  • サイドバーにあるMenu1やMenu2などの見出しは、#sidebar以下のh2
#main h2 {
  ...
}
#main .contact .header h2 {
  ...
}
#sidebar h2 {
  ...
}
.somewhere .title h2 {
}

ほかどこかでまたh2が登場したら、その文脈に合わせてセレクタを書いていこう……と。

このような書き方は、もちろんありえますし、CSSの文法になんら背いているわけではありません。しかし、OOCSSは、このような書き方をしていると、いずれ設計が破綻すると考えます。OOCSS的には「ダメなCSSの書き方」です。

なぜダメなのか?

さきほどのコードは、それぞれ、h2にスタイルを当てています。メインエリアのh2にはコレ、サイドバーのh2にはコレ、お問い合わせブロックのh2にはコレ……といった指定方法です。

では、なぜそのような書き方がダメなのでしょうか。OOCSSは、さきほどの書き方を「場所に依存した指定方法だからダメ」であると考えます。このような書き方を続けていけば、以下のような問題が発生すると指摘します。

上書き合戦

あらかじめh2にスタイルを当てていたとしましょう。当てたスタイルは、marginborderpaddingの3種類です。さきほどの例のように、h2をいろいろなところで使うとすれば、このh2に当てたスタイルを、部分的に上書きする必要が出てくるでしょう。これは無駄なことです。

さらに言えば、自分がいままさに新しく使おうとしているh2には、何らかのスタイルが、どこかで指定されているかもしれないのです。例えばここでは、#main h2として、メインエリアのh2すべてに、スタイルを適用してしまいました。ほかのh2がメインエリアに登場したら、どうするか? これは、自分がこれから書くセレクタで打ち消すしかありません。こんなふうに、スタイルの上書き合戦が積み重なっていってしまうことがあります。

コピー

まぁそもそも、h2にそこまで具体的なスタイルを指定してしまうのは、よい考えではない。いろいろなところでh2を使うのであれば、メインエリアのh2には、そこまで具体的なスタイルを当てない方が、よかったのかもしれません。よし、h2に細かいスタイルを当てるのをやめよう! と考えたとしましょう。しかしそうすると、今度は別の問題が発生してきます。

見栄えが同じ見出しがいろいろなところに登場した場合、それぞれのh2を含むセレクタについて、同じスタイルの指定をコピーして使わなければならなくなってしまうかもしれません。

幸いなことに、CSSでは、複数のセレクタを、,(カンマ)を使い、一括して指定することができます。同じスタイル群が複数の箇所で使われたら、セレクタをまとめるとよいと考えるかもしれません。しかし、部分的にだけ共通したスタイルだったなら、どうすればよいでしょう。共通のスタイルだけをまとめ、それ以外のスタイルはまた別に指定する……などといったことも可能ではありますが、そのようにして、できあがったコードは、非常に複雑なものとなってしまうでしょう。

詳細度

さきほどの上書きし合う問題ですが、この上書き合戦に勝利するには、詳細度の理解が不可欠です。

CSSにおいては、詳細度というものが存在します。詳細度は、どのセレクタ群が優先されるかを決めます。使用しているセレクタにおいて、何セレクタを使えば何点加算されるとあらかじめ決まっており、合計点が高いセレクタの指定を優先する——といったルールです。この詳細度をきちんと理解できず、当てたいスタイルの詳細度が、競合しているどこかのスタイルよりも低ければ、自分の書いたスタイルは反映されないのです。残念。

!important

でも、そのような詳細度を気にしなくてもスタイル効かせることができる!importantというものがあります。これを使えば、以降、もうそのスタイルを二度と打ち消すことはできなくなります。

このようにして、CSSファイルの中は、!importantだらけになっていきます。あとから修正したいからといって、前に書いた部分を直すわけにはいきません。なぜなら、あなたの書いたスタイルは、すでに別のどこかで使われているかもしれません。そのスタイルを変えてしまったら、問題が起きるかもしれないのです。

それに、そんな昔に書いた部分を変更したって、反映される保証はないのです。どこかに書かれた別のセレクタが、あなたのスタイルを上書きしているかもしれないのですから。つまり、あなたにできることといったら、CSSファイルの末尾に、!important付きで新しいスタイルを追加することしかないのです。

このようにして、CSSファイルは、徐々に肥大化していくのです。

ダメな理由

ここで挙げたような例は、かなり極端な例です。しかしながら、ある程度のページ数のあるWebサイトのHTML/CSSを書いたことがあったり、複数人で作業したことのある人であれば、ここに書いたような問題が起こりうることは、容易に想像がつくのではないでしょうか。

筆者はCSSの末尾に書かれた/* 20XX年 田中 追加 */というようなコメントとともに書かれた、!important付きのスタイルを見て、げんなりしたことがあります。

OOCSSは、このような状態に陥ってしまう原因は、前述したように「場所に依存した指定方法だからダメ」であると考えます。場所に依存してスタイルを書くのが通じるのは、テンプレートの少ない、ごく小規模のサイトだけ。スケールすることを考えたら、このやり方には限界があると考えます。

レゴのように考える

じゃあどうするかという問題ですが、Nicole Sullivanは「Webページを、レゴの集まりで考えよ」と言います。

ページを構成するパーツを、レゴのパーツのように分け、それをページにどんどん積んでいくと考えなさい。Webページは、レゴの集まりなのです。そして、ひとつひとつのパーツは、そのパーツで完結するようにCSSを書きなさい。そうすれば、さきほど例に挙げたような破綻は起こりません、とOOCSSは言うのです。

これがOOCSSの基本的な、キモとなる考え方です。最初の例で考えてみましょう。

この教えを実践するためにはまず、ひとつひとつのパーツに名前を付ける必要があります。例えば、さきほどの見出しの例であれば、headingheading2heading3といった具合です。

そして、HTMLでは、その名前をクラス名として指定し、CSSでは、そのクラスセレクタから始め、スタイルを当てていきます。例えばこのような形です。

<h2 class="heading">Title</h2>
...
<h2 class="heading2">Contact</h2>
...
<h2 class="heading3"><span>Menu1</span></h2>
...
<h2 class="heading3"><span>Menu2</span></h2>
/* heading module */

.heading {
  prop: val;
}

/* heading2 module */

.heading2 {
  prop: val;
}

/* heading3 module */

.heading3 {
  prop: val;
}
  .heading3 > span {
    prop: val;
  }

このようなコードを書くことで、headingheading2heading3という、3種類のレゴパーツを作ったことになります。こうやってひとつひとつのパーツを別々に作っていき、ページを作るというわけです。

OOCSSでは、このひとつひとつのレゴのパーツをCSSオブジェクトと呼んでいます。ただ、便宜上、このシリーズでは「モジュール」と呼ぶことにします。

このような考えを守ってコードを書けば、同じパーツを二度コーディングする必要がなくなりますし、上書き合戦のような不毛なことも起こりません。

これがOOCSSの基本的な考え方です。

しかし悩む

なんだか、こうやって話だけを聞くと、とっても合理的でうまく行きそうな気がするかもしれません。しかし、これを読んだ人の中には、こう考える人もいるでしょう。

「言ってることはわかる。だけども、実際にコーディングするときはそんなにうまくいくことなんてないんだよ。そこが困ってるところなんだよ。事件は会議室じゃなくて現場で起こってんだよ」と。

まったくもってその通り。そして、そこが難しいところです。

例えば、今回のサンプルとして出した見出しの例ですが、実際にコーディングをする際、こんなに単純にモジュールを切り分けられるものでしょうか。

例えばheading2と名付けた、問い合せブロック内の見出しですが、これを単独の「見出し」モジュールとして考えるか、それとも、「問い合わせブロック」というモジュールとしてひとかたまりで考えるかというような悩みがあることが想像できます。

サイドバーにある見出しについて考えてみれば、この記事の中にある図は、ただのポンチ絵だから、heading3と名前を付ければ最適のように見えます。しかし、そのすぐ下にテキストのナビゲーションがあり、それと一体化したようなデザインであれば、それをひとかたまりで考えた方が自然かもしれません。

このように、何をひとつのモジュールとして考えるかは、非常に判断の難しい問題です。筆者は、これを決定するための絶対的な指針は存在しないと考えています。何がモジュールかというのは、ページデザイン上のルールであったり、HTML/CSS/JavaScriptの実装上の都合などに左右されるのではないでしょうか。そのあと、例えばCMSなどに組み込むこともある場合は、その実装に合わせてモジュールを考えた方が、うまくいくかもしれません。

そのような種々の実装上の都合を考えた上で、不都合がない、合理的なコードに落とし込むのが、HTMLテンプレートを設計する人に求められる役割かもしれません。

そのようなHTMLテンプレートの設計を行う上で、OOCSSが言う「レゴパーツの組み合わせ」という考え方は欠かせないと、筆者は考えています。筆者は、OOCSSを知るずっと前から、この考え方で開発を行ってきましたし、同じように考えてコードを書いていた人はたくさんいるでしょう。

次回は、OOCSSの「スキン」という考え方を解説していきます。