未経験からエンジニアとしてweb制作会社へ入社し、はじめはscssのネストを利用して少し便利にcssを書く事ができる程度の知識でしたが、ページ数の多いwebサイトを制作・保守するうちにcss設計の大切を痛感し、どうすれば良いcss設計ができるのか、ひたすら考えた結果、ある程度自分の中でかたまってきたので書きます。
そもそもcssが破綻するとは
cssを書いたことのあるかたなら皆が通る道ですよね。
だれもが経験するcssが破綻したといえる状況をいくつかあげ、cssが破綻するとはどのような状況を指すのか説明します。
今から紹介するような事が起きないcssを書く事が良いcss設計と言えるでしょう。
詳細度を高めて上書きする
最も頻繁に発生するのではないかと思う『値の上書き』
あるデザインを再現したが、別ページで色が違う、文字の大きさが違う、など派生形が存在し、color,font-sizeなどを上書きするというようなパターンは多くの方が経験していると思いますが、適当にcssを書くと、以下のように詳細度を高めることで対応することになります。
上の2パターンのデザインを破綻しやすいcssの書き方で再現してみます。
<!-- パターン1 青背景--> <div class="background-black-area"> <div class="main-visual"> <div class="page-title">ページのタイトルが入ります。</div> <a class="btn" href="#"> <i class="mail-icon"></i> <span>お問い合わせはこちら</span> </a> </div> </div> <!-- パターン2 白背景--> <div class="background-white-area"> <div class="main-visual"> <div class="page-title">しろ背景にしたい!</div> <a class="btn" href="#"> <i class="mail-icon"></i> <span>お問い合わせはこちら</span> </a> </div> </div>
// パターン1 青背景 .main-visual { background-color: #2051C2; padding: 150px 100px; & .page-title { color: #fff; font-size: 42px; font-weight: bold; } & .btn { display: flex; justify-content: center; align-items: center; background-color: #fff; width: 300px; border-radius: 3px; padding: 16px 16px; margin-top: 28px; & .mail-icon { width: 15px; height: 10px; @include mailIcon(#2051C2);//こちらはメールアイコンを表示するためのオリジナル関数:無視で良い } & span { font-weight: bold; margin-left: 10px; font-size: 16px; color: #2051C2; text-decoration: none; } } } // パターン2 白背景にするために初めのセレクタより、ネストし、詳細度を高めることで値を上書き .background-white-area { & .main-visual { background-color: #fff;//←上書き border: solid 1px #000;//←上書き & .page-title { color: #2051C2;//←上書き } } & .btn { background-color: #2051C2;//←上書き & .mail-icon { @include mailIcon(#fff);//こちらはメールアイコンを表示するためのオリジナル関数:無視で良い } & span { color: #fff;//←上書き } } }
このように、値を上書きするために詳細度を高めるようなcssの書き方をすると破綻しやすくなります。
今回は簡単&パーツのみでしたので、少し実感しにくいですが、実際は、このパーツを囲う親などがあり、html構造が複雑になります。
その状態で次はボタンのサイズを変更して欲しい、制作途中で○○のページだけボタンの色をオレンジにしたい、など、デザイン変更の依頼が来た場合にすぐに対応できるでしょうか?
3パターン目にあたりにはいってくると、こちらの例ですら「あれ、値が上書きされない」という現象が起こってきそうです。
「あれ、値が上書きされない」となった時点でcssは破綻していると言えます。
詳細度がたかすぎて!importantを使う
上記の「詳細度を高めて上書きをする」の延長戦上に存在します。
詳細度が把握しきれず、上書きされない現象を解決するために「!important」を使用して強制的に値を上書くしかない状態に出くわすことがありますが、!importantを使用した時点でそのcssは破綻していると言えます。
!importantを使用するとこれ以上、上書きすることができなくなってしまうので、後々の変更に対応できなくなり、保守がとても大変になります。
別の場所のデザインがくずれた
Aのページのデザインを変更するとBのページのデザインが崩れる。
Cのパーツの上下の余白を変更するとDのパーツの余白が変わってしまう。
影響範囲がcssを書いた本人ですら把握できず、意図しない場所のデザインが変わってしまうcssは破綻していると言えます。
自分で影響範囲を認識しておくことはもちろんですが、他の人がドキュメントを見たり、少し調べるだけで影響範囲を理解できる設計にすると保守性が向上します。
良いcss設計とは
良いcss設計とはどういったものでしょう。
良いcss設計についてgoogleのすごいエンジニアの方が提唱しているのでそちらを参考に自分なりの解釈を加えて説明します。*原文を翻訳し、載せているわけではなく、あくまで自分の経験からの考えになります。原文はリンクからどうぞ。
- 予測できる
- 再利用できる
- 保守できる
- 拡張できる
予測できる
class名からそのclass名を付けたパーツがどのようなデザイン、挙動をするのか予想きる。
また、あるclassの値を変更した際にどこが変化するのか影響範囲を予測でき、意図しない個所が変更されるという事を初めから回避できる。
『よくないcss設計』であげた、Aのページのデザインを変更するとBのページのデザインが崩れた何てことが起こらないcss設計が良いcss設計です。
再利用できる
Aの場所で使用したclassを使う事で新たにBというパーツを作ることができる。
『よくないcss設計』で上げた、詳細度を高めて上書きするの例で挙げた2パターンのデザインがあるような状態です。これ↓
似たようなデザインだが、若干違う。
この2つのデザインをコーディングするのにそれぞれコーディングしていてはcssの重複が起こり、コード量も膨大になり、同じものを記載する分、開発速度も落ちてしまいます。
ですので、過去に記載したcssを使いまわすことが良いcss設計ですが、値を上書きしまくると詳細度が把握しきれず、!importantを使用することになり、破綻してしまうので気を付けなければいけません。
保守できる
webサイトの最も大切な部分はそのサイトを運用し、利益を出すことです。
サイトを作ることよりも運用時の方が大切なのです。
運用フェーズに入ると、この機能を追加したい。ABテストのためにここのデザインを変更したい。というようなことが必ず発生します。
その際に、パーツを追加しただけでデザインが崩れる。パーツの場所を変更したり、html構造を変更するだけでデザインがくずれるというようなことが起こるとその部分を治すだけで一苦労です。
そのようなパーツの追加、変更を行っても崩れることのないcss設計が良いcss設計と言えます。
htmlを変化させたり、パーツを追加するとデザインが崩れるとはどのようなものか
拡張できる
上3つの『予測できる』『再利用できる』『保守できる』を包括したようなものな気がします。
『拡張できる』の説明に関しては、css設計について解説している他のサイトを見ると『他のエンジニアが途中参戦しても同じ品質でコーディングできる』というような説明が多いです。
この誰でも同じ品質で制作することができるためには、css設計をきちんと言語化し、class名を見たり、少し調べれば挙動や影響範囲を知ることができるため、パーツを再利用でき、再利用先で使用してもくずれることのない保守性の高さが必要です。
破綻しにくいcss設計を行おう
ベースはFLOCSS
ベースの設計はFLOCSSで行っています。
今のところFLOCSSの設計が最も破綻しにくい設計ではないかと感じます。
この記事ではFLOCSSのルールについての基本的な説明は行いません。
では何を解説するかというとFLOCSSの具体的な使用方法と、自分の経験からプラスして、こういうルールやTipsを使用するとよりよいcssを書く事ができますよ。という部分を解説していきます。
公式の内容をきちんと理解する。そこがとても重要!
解説の内容はFLOCSS自体の内容が半分を占めますが、ここがとても重要です。
というのも、私が初めFLOCSSを勉強し始めた時、まず、公式を読みました。ですが、「公式を読んでも何を言っているのかわからない」「なぜそうするのかわからない」「そのルールの重要性がわからない」と、わからないことだらけでした。
ググってみてもFLOCSSについて解説されている記事はFLOCSSの一番の特徴である、ファイル分けについてザックリ解説しているだけなんですよね。
でも実際はファイル分け以外のルールの方がかなり大切です。
意味が分からないと重要性を理解することができないので、実際の案件に使用することがなく、FLOCSSのファイル分け以外の項目をすっかり忘れたまま案件をこなしていました。
実際に案件をこなすうちに何度かcssが破綻する場面があり、リファクタリングを繰り返し、css設計につて試行錯誤していくうちに次第に破綻しにくいものを作ることができるようになりました。
より良いcss設計を求め、勉強中に改めてFLOCSSの公式を見直していると、驚いたことにリファクタリングを繰り替えす事で行きついたcss設計とFLOCSSのファイル分け以外の項目(理解できなくて見逃していた部分)と同じなんですね。
恐らくFLOCSSを使用しているがcssが破綻してしまうという方はFLOCSSのちゃんとしたルールを知らないままcssを書いているのだと思います。
そのFLOCSSの、重要で理解しにくい部分を身に着けることは実務を経験していても難しいと思います。
FLOCSSについてはこちら(公式git)を参照に基本のルールを勉強してください。
FLOCSSの重要なルール
FLOCSSで最も躓く、かつ、重要な部分はパーツ分けです。
FLOCSSでは大きい枠順でLayout,Project,Component、そして調整用としてUtilityと分け、それぞれで役割分担する事でcssを管理しやすくしています。
その中でもProjectとComponentの区別はとても難しく、この分け方をミスるとcss設計がうまくいかなくなるので、とくに重要となってきます。
そんなProjectとComponentの分け方について解説します。
また、Utilityの使いどころも難しい所があるのでそちらの解説も行います。
Component、Project、Utilityの分け方
Layoutに関してはこちらでは解説しません。
Layoutは重要度が低く、分け方で迷うことはあまりないのでFLOCSSの公式を見てください。
ただ、公式にLayoutaはidを使用してもよいとありますが、なるべくidは使用しないほうが良いです。
理由としては以下があげられます。
- FLOCSSはBEMを使用してclass名を記載しますが、BEMにidを使用してはいけないというルールがある
- idはJava Scriptから使用する事や、アンカーリンクで使用する事が多いので空けておきたい
- css設計の思想が再利用性なのに対し、idは再利用とは逆のことをしている
これらの理由からidを使用するのは非推奨です。
Componentの定義
Componentはそれ 1 つで機能する最小のパーツです。
【例】メインビジュアル
こちらのメインビジュアルの場合、『ページのタイトルが入ります。』部分(タイトル)とお問い合わせボタン、の2つのComponentにわけることが正解と考えます。
【ダメな例(小さすぎる)】
アトミックデザインなどの設計手法では原子(存在できる中で最も小さなパーツ)で分ける場合があります。
例えば今回の例でいうメインビジュアルなら、ボタンの中にテキストとアイコンがありあますが、ボタンの中のテキストとアイコンを別の素材として分け、2つのパーツとそれを包む親という分け方をします。
その際の判断基準にパワポの図形で表すことができるかという基準がありますが、アトミックデザインに適した考え方なので使用しません。
このようにタイトルとボタンに分け、さらにボタンをアイコンをテキストに分けるやり方は小さく分けすぎです。
そうではなく、ボタンを最小の単位とみなします。
アイコン、テキストを最小とするかしないかの判断の基準は、分けた時にそのパーツがなんらかの機能を持つかどうかです。
ボタン内のテキストだけでは機能は持っていません。
アイコンも同じく、アイコンだけ存在しても何かの機能があるわけではありません。
ボタンの枠、テキスト、アイコンがそろって初めて、ボタンとしての機能を持つことができますので、今回の設計の場合、ボタンが最小パーツ(Component)となります。
アイコンはいろいろな箇所で全く同じものを使いまわすことが多いのでComponentとして扱たいところですが、FLOCSSにはComponentの中にComponentを入れることは許していません。
ですのでFLOCSS のルールにはありませんが、Componentと分けるために『i-』というプレフィックスを持たせて運用するという方法がおすすめです。
Projectの定義
Componentを集めて何か意味を持つ集合体ができればそれはProjectです。
【例】メインビジュアル
ProjectはComponentの集合体なのでタイトルとボタンを囲う親になります。
そしてProjectはほとんどが位置決めのために使用されます。ですので、OOCSSのスキンに当たる部分(color : red;などの見た目や装飾に関する部分)を書く事はほぼありません。
Utilityの定義
UtilityはComponentのスタイルやProjectの位置決めの微妙な調整などを行います。
Utilityを使用して微妙な調整する事でComponentやProjectのバリエーションをむやみに増やしてしまう事を防ぎます。
例えば、以下のような余白を持つデザインが存在したとします。
『見出しレベル2』のデザイン(左)の上下の余白を100pxにしたい。『見出しレベル3』のデザイン(右)の上下の余白を同じ100pxにしたい。
同じ余白を使用するのでcssを共通化できると思い、共通化して運用していると、『見出しレベル2』のデザインは中のコンテンツが24pxのh2に対し、『見出しレベル3』のデザインは中のコンテンツが20pxのh3になっており、さらに最後に画像が入っていいます。
ご存じの通り、文章はラインハイトによって上下に余白が付いてしまいます。
その影響により、緑のpx値が共通化してcssから付けるmarginの値ですが、その値に対して赤のpx値が実際に再現したい余白(100px)とは異なる値になってしまっています。
同じ余白を再現するために同じmarginを使用しても、コンテンツの内容によって見た目上の余白の値が変化してしてしまうのです。
同じ余同 = 同じmarginを付ければ良い
これは間違いです。
ではこの問題を解決するにはどうすればいいでしょうか。
単純に考えると、共通化する事をやめて、コンテンツの内容に合わせて、適したmarginを持つProjectを作るという方法があります。
ですがその場合、使用できる場所は限られ、cssが複雑化し、どんどん再利用性、保守性が失われていきます。
ですので、共通化はします。
同じProjectを使いつつ、Utilityを使用して微妙な調整を行うのです。
その他にも、ここだけでフォントの太さを変えたい。色を変えたい。など、
- 一か所
- ピンポイント
- 変更するcssを書く際に1,2行で書ける
上記のようなものはUtilityで対応します。
識別子、プレフィックスを付ける
class名にはそのclassがLayout,Project,Componentのどの分類に属しているのかしきべつしやすくするためにclass名の先頭にプレフィックスをつけます。
- Layout => l-***
- Project => p-***
- Component => c-***
- Utility => u-***
そして、オリジナルですが、アイコン系のclassに『i』のプレフィックスをつけます。
- Icon => i-***
これは、FLOCSSのルールを守るためです。
アイコン系はいろいろな箇所で使いまわせるので、Componentにしたいところですが、Componentの中にComponentを入れることはできません。このルールを守りつつ、Componentのような扱いを出来るよう、新たにi-というプレフィクスを追加しました。
そしてもう一つJava Scriptで使用するclassには『js-』のプレフィックスをつけましょう。
- Java Script => js-***
こうすることで、このDOM要素はjsからなにか影響されるという事がわかります。
class名の付け方はBEMを使用する
class名は『予測しやすい』『バッティングしにくい』を意識する必要があり、それを満たすためにBEMを使用します。
BEMの命名ルールは『block__element–modifier』こうです。
それぞれ
- block:いくつかの要素を囲う親
- element:blockのなかの子要素
- modifier:派生形を作りたい場合、これ付けて区別する
と分かれており、それぞれの繋ぎはblockとelementの間は『__』アンダースコアが2つ、elementとmodifierの間が『–』ハイフン2つとなっています。
BEMを初めて聞いた方はこれだけを説明されてもピンとこないかもしれません。
さらにBEMはFLOCSSと合わさると解釈が少し難しくなり、きちんとルールを決めないとコードを書く人の感覚に左右されてしますので良くないですので具体例を用いて説明します。
【具体例】
このメインビジュアルをBEMを使用してclassを付けてみます。
<div class=""> <div class=""> ページのタイトルが入ります。 </div> <a class="" href="#"> <i class=""></i> <span>お問い合わせはこちら</span> </a> </div>
いったんclass名を空にしてhtmlをかきました。
まず、一番親から行きましょう。
こちらはメインビジュアルを囲う一番の親ですので、blockに当たります。
class名はmain-visualにしておきましょう。そしてFLOCSSのプレフィックスを付けます。Componentに分類できるタイトルとボタンがあるので、それらを囲う親はProjectですね。↓
<div class="p-main-visual"> <div class=""> ページのタイトルが入ります。 </div> <a class="" href="#"> <i class=""></i> <span>お問い合わせはこちら</span> </a> </div>
次にページのタイトル部分のclassを考えます。
ここでBEMとFLOCSSを一緒に使うと難しくなるポイントが出てきます。
BEMのルールではblockの子要素はelementとなりblock__elementという形でclass名を付けなければいけません。
ですのでこの場合、タイトルはp-main-visual__title?(プレフィックスはblockにくっついているので同じblockを受け継ぐということはプレフィクスも受け継ぎます。)↓
<div class="p-main-visual"> <div class="p-main-visual__title"> ページのタイトルが入ります。 </div> <a class="" href="#"> <i class=""></i> <span>お問い合わせはこちら</span> </a> </div>
あれちょっと待ってください、タイトルはComponentだったはず…
ここの判断が難しいところです。
blockの子要素なのでblock__elementというルールはあるがComponentにしたい。
それならこのmain-visualごとComponentという扱いにする?
このサイズのテキストとボタンは別の場所で使用しなさそうだし、いいか…
でも、FLOCSSのルールに厳密に従うとタイトルとボタンはどう考えてもComponentだし…..
この状況でメインビジュアルをProjectとして採用すると、そのルールでcss設計がきまってしまします。(途中でcss設計ルールを変更すると破綻の原因になりますので)
大きな影響を与えるのでいろいろ悩んで全くコーディングが進みませんよね。
ややこしくなる原因はBEMの解釈の仕方にあります。
BEMの解釈
全てProjectと解釈すればいいのか、Componentと解釈すればいいのか、ProjectとComponentを混在させていいのか、混在させるとすると境目はどこにすべきかという疑問が生まれますが、この疑問が出てくる原因はBEMの解釈が人によって異なるからだと考えます。
様々な解釈の仕方がありますが、これから説明する解釈方法は私の方法であり、すべてにおいてこの解釈が正しいというわけではないという事を前提としてください。
BEMはオブジェクト指向のようにとらえよう。
block__element—modifier
↓↓↓↓↓↓↓↓↓
class__method—サブクラスのmethod
Java Scriptで言うClassを作り、子要素はmethodとして扱い、modifierはサブクラスのmethodとして置き換えてイメージします。
BEMの目的は名前の重複による予期せぬうわがきを防ぐ事です。
これはcssが全てグローバル参照だから起こる問題です。
グローバル参照によっておこる問題を解決するにはプログラム言語のようにスコープを使って安全に使用できるようにします。
scssでスコープを作るには
.prelent & .child
とすることで影響範囲を限定できます。
ですがこのままですと、htmlを見た時にclass名は同じなのに挙動が全く異なり、混乱の原因になります。
htmlを見ただけでわかるように、prelent__childとし「prelentの中でしか使用しないchildですよ」と、一目でわかるようにします。
この働きをオブジェクト指向のClassとmethodで考えると理解しやすくなります。
ポリモーフィズムなんかはまさにそうです。
BEMはnamespaceの役割を果たしている
BEMはnamespaceの役割を果たしていると言えます。
–modifierは、継承してメソッドを書き換えているととらえましょう。
例えば、ほとんど同じデザインで、背景色のみ異なるパーツが存在した時、すべてのcssを2回書くと、重複が発生します。
これはオブジェクト指向の継承を使って重複を防ぐ方法とおなじように、–modifierを使い、差分のみ上書きし、実装します。
Componentから制作する
BEMの解釈ともう一つ大切な事はProjectとCompornetの制作順です。
BEMとFLOCSSが合わさると混乱する原因は、BEMの命名の順が上から(外側から)という点。
blockはProject、elementはComponentというイメージが強い点。
この2つです。
この2つのイメージに囚われないためにはデザインカンプからコーディングに入る際に、Componentを先に洗い出し、制作する事が大切になってきます。
Project→Componentの順で制作すると
<div class="p-main-visual"> ↓↓↓ <div class="p-main-visual"> <div class="p-main-visual__title">title-text...</div>
というように、本来Compornent要素になるDOM要素をelementにしてしまいそうになりますが、Component→Projectの順で制作する事で
<div class="c-main-title">title-text...</div> ↓↓↓ <div class="p-main-visual"> <div class="p-main-visual__title"> ↓↓↓ <div class="p-main-visual"> <div class="p-main-visual__title"> <div class="c-main-title">title-text...</div>
というように正しくマークアップする事が可能です。
cssの書き方
Component、Projectを制作する際のcssの書き方にルールを設けます。
- ネストは基本してはいけない
- ただし、WordPressエディタ対応やブラウザ対応の場合など、あえてスコープを切る必要がある場合を除く
例えばこういう事ですね。
//ネストして依存関係を生み出している .p-main-visual { background-color: #2051C2; padding: 100px 100px; & .p-main-visual__title { color: #fff; font-size: 42px; font-weight: bold; } } //ネストしない .p-main-visual { background-color: #2051C2; padding: 100px 100px; } .p-main-visual__title { color: #fff; font-size: 42px; font-weight: bold; }
今回の場合、こういう親の中に子を入れる(ネスト)での書き方もできるわけですが、これは親に依存するのでよくありません。
親に依存することがダメな理由は『htmlを変化させたり、パーツを追加するとデザインが崩れるとはどのようなものか』のスライドでも解説した通りです。あれは『>』を使用し強力な依存関係を作っていましたが、ネストでの依存関係も破綻につながるのでダメです。(主にProjectとComponentの間で)
といってもBEMを使用している場合、blockとelementで依存関係を作りだすことでスコープを切り、安全に使用しているので関係ないですが、意識しないとProjectとComponentの間でネストして書きがちなので、もう、一律でネストは基本禁止です。
ただ、例外があります。それはWordPressのエディタから制作されたコンテンツにあてるスタイルだったり、ブラウザ対応の場合です。
WordPressの場合、エディタから生成されるコンテンツはclassがついていません。
ですのでスタイルをdiv,p,a,liなどhtmlタグに直接当てていくことになるのですが、これはcssの七つの大罪に当たります。
htmlタグに直接スタイルを当てると、cssはサイト全てに影響されるので、すべてのpタグにマージンがついたりします。マージンを付けたくないpがあった場合毎回打ち消しのスタイルを書いていると発狂します。
デフォルトでついてるスタイルと同じです、かならずリセットしますよね、あれを全てのcssで行う羽目になるので禁止です。
WordPressの場合はエディタからのコンテンツが入る親にclassを与え、そのクラスの中のdiv,p,a,liなどにのみスタイルを当てるという事をし、他のページが汚染されることを防ぎます。
ブラウザ対応も同じようなことです。
ネストをせずにcssを書くためにはすべてのタグにclass名を付ける必要がある
ネストを使用せずに書くという事は以下のような書き方ができないという事でもあります。
.p-main-visual { & a { display: flex; justify-content: center; align-items: center; background-color: #fff; width: 300px; border-radius: 3px; padding: 16px 16px; margin-top: 28px; } }
このように書く事ができないので、aタグには何かclassを与えなければいけません。
ネストを禁止する理由は必ずタグにclassを付けさせるという目的もあります。
これも依存関係を防ぐために行います。
もし、このような書き方をするとaタグ以下のコンテンツを別で使いまわそうと思い、移動さた時にデザインが崩れてしまいます。
ですので、必ずclassを付けるのです。
ただComponentの場合、それが最小単位であり、それ以上分解して使用することがないのでComponentの子要素はclassを付けなくても大丈夫です。
ですが、これはok。これはダメ。と複数のルールがあるとややこしくなるので基本はComponentでも、どこでもネストは禁止です。
実際にこのルールを使用しているとわかるのですが、すべてに異なる名前を付ける必要があるのでclass名を考えることが大変になります。
そのため、top-page-main-visual__element-hogeというようにめちゃくちゃclass名が長くなりがちです。
ここにmodifierを付けると以下のようにさらに長くなります。
<div class="p-top-page-main-visual"> <div class="p-top-page-main-visual__title p-top-page-main-visual__title--red"> ページのタイトルが入ります。 </div> 以下略
これを避けるために以下のルールを設けます
- ハイフン繋ぎは一回まで
- modifierを付ける際は先頭の重複部分を省き、『-』ハイフンから始める
ハイフン繋ぎは一回まで
先ほどtop-page-main-visualとハイフンを3回使用しましたが、これを1回に制限します。(プレフィックスとの繋ぎのハイフンは除く)element部分も同じです。
<div class="p-main-visual"> <div class="p-main-visual__title p-main-visual__title--red"> ページのタイトルが入ります。 </div> 以下略
modifierは重複部分を除き、ハイフンから始めます
<div class="p-main-visual__title p-main-visual__title--red">
これ↑がこうです↓
<div class="p-main-visual__title -red">
この他にハイフンから始まるclass名を付けるルールはありませんので、ハイフンから始まるclass名があれば、確実にmodifierだとわかるという事が前提です。
ハイフン繋ぎを制限すると「class名を考えるの大変です」問題が発生する
class名の長さに制限をかけると名前を考えることが大変になりますが、そこはmodifierを使って解決します。
例えば同じメインビジュアルでもTOP と下層ページでデザインが異なる場合
//トップページ <div class="p-top-page-main-visual"> //下層ページ <div class="p-lower-page-main-visual">
こう↑ではなくこうです↓
//トップページ <div class="p-main-visual -top"> //下層ページ <div class="p-main-visual -lower">
ですが、あくまでもmodifierは『派生』ですので、大きく異なる場合は以下のようにします。
//トップページ <div class="p-home-visual"> //下層ページ <div class="p-main-visual -lower"> <div class="p-main-visual -hoge">
ルールのおさらい
- ネストは基本してはいけない
- WordPressエディタ対応やブラウザ対応の場合など、あえてスコープを切る必要がある場合はネスト可能
- ハイフン繋ぎは一回まで
- modifierを付ける際は先頭の重複部分を省き、『-』ハイフンから始める
プロパティを分けて考える
実際にcssを記載する際に、cssプロパティをComponentに記載するか、Projectに記載するか悩みますが、プロパティを分けて考え、分類ごとにどこに記載するのがおすすめなのか把握すれば簡単です。
まず、OOCSSの考えを取り入れ、パーツを構造とスキンに分けます。
さらにポジション、サイズも追加して以下のようになります。
- 構造…background-image,display,overflow.変数の定義など
- スキン…color,background,animetion,borderなど
- ポジション…margin,position,gridなど
- サイズ…font-size,with,paddingなど
そしてこれらをComponentとProjectに割り当てます。
ポジション系はProjectの役目であり、Componentが持つべきものではないのでProject一択ですが、それ以外は、この粒度の分け方(4分割)ではProject、Componentどちらにも必要なプロパティがありますので、〇と◎で表しています。
◎の意味は『なるべくその中で使用する』or『その中で使用するプロパティが多い』です。
〇の意味は『使用してもよい』となります。
このままですと、ポジション以外曖昧ですのでさらに分割します。
分割にはcssの継承が関係してきますので、まずは継承について説明します。
ProjectとComponent間での継承問題
cssには継承という機能があります。
継承とは親のスタイルを子が引き継ぐ事です。
詳しくはMDNを参照:カスケードと継承[MDN]
継承は、わざわざcssプロパティを毎度記載しなくてもよくなるのでありがたい機能ではありますが、ComponentとProjectで分けるcss設計を行っていると厄介な部分が出てきます。
例えば以下のようなcssを記載した場合
<div class="p-hoge"> <div class="c-text">text...</div> </div>
.p-hoge { font-size: 20px; margin-top: 32px } .c-text { color: red; }
c-textはフォントサイズ20pxの、文字色が赤の見た目をしたパーツだったとします。
そして上方向の余白が32px空いています。
サイズ系はProjectが持った方がよいという事でfont-sizeをProjectに記載し、スキン系はComponentに記載したとします。
余白は必ずProjectに持たせるものなのでmargin-top:32pxをProjectに記載します。
もちろんこれでも望み通りのデザインを再現する事は可能です。
ですが、別の場所でこちらと同じ、フォントサイズ20pxの文字色が赤のパーツを使いまわすため、c-textを移動させると(Projectは一緒に移動しません)フォントサイズのスタイルをProjectから継承してスタイルを当てていたため、フォントサイズがデフォルトの16pxに戻ってしまいます。
このように継承によってスタイルの依存関係を生み出すことは良くないので、継承が発生するcssプロパティにはなにかルールを設けて対策を考えなければいけません。
ですので、スタイル特長毎にわけたものをさらに継承の有無で分け、Project、Componentどちらに記載すればいいのか整理します。
さらに以下のルールを追加します。
- 異なるblock間でのスタイルの継承の禁止
異なるblock間でのスタイルの継承の禁止とは、つまり、以下のようになります。
プロパティの値をp-articlesからp-articles__innerへ継承する事は許可します。理由はこの2つのclassは同じブロックだからです。
一方、particlesからparticleへプロパティを継承する事は禁止です。この2つのclassは同じブロックではなく、別のブロックだからです。
参考:CSSプロパティ一覧
モディファイアの影響範囲
cssの継承問題をおこないましたが、BEMのmodifietを使用する際に似たような問題が出てきます。
それはmodifierの影響範囲をどうするか。elementのスタイルをmodifierで変更したい場合、modifierをどこにつけるのが適切かという部分です。
modifierを付ける位置の運用パターンは以下の2種類があります。
- 各classにつける
- 一番親のclassにつける
①各classにつける場合
<div class="c-button"> <div class="c-button__icon -size_S"></div> <div class="c-button__text -size_S"></div> </div>
①のデメリット
1-1. class名がかさばる → そこまで大きな問題ではない
1-2. マークアップに時間がかかる → ルールを守らずに近道する可能性がある。これは大きな問題。
1-3. 運用が複雑化する事で制作者とは別の人がルールを把握しずらい → 本末転倒。大きな問題。
②一番親につける場合
<div class="c-button -size_S"> <div class="c-button__icon"></div> <div class="c-button__text"></div> </div>
②のデメリット。
2-1. 依存関係ができる → Componentはそれ以上分解しないので関係ない。
2-2. モディファイアの上書き問題が発生する → 問題
2-3. パターン分のモディファイアを用意する必要があり、重複コードが発生しやすい。→問題
結論
どちらの運用もメリット/デメリットありますが、一番避けたいのは1-3の複雑化し、本末転倒になる事です。
ですので、②の親のclassにmodifierを付け、中のelement要素に影響を与える方法を取ります。
ただし、ProjectとComponentで互いに影響をあたえる事は禁止。
別のblockから別のbolckに影響を与えるのは禁止。(主にProject。Componentはblockの中に別のblockが存在しない前提)
というルールがあります。
こちらのルールはスタイルの継承の部分と同じです。
マルチクラス
マルチクラスとは1つのDOMに複数のclass名を与える事です。
ほとんどのcss設計はマルチクラスを使用していますが、マルチクラスは依存関係をうみだしていまいます。
チームのメンバーがその依存関係を把握するためにはパーツリストやストーリーブックなど、ドキュメントが必要になってきますので負担になります。
ですが、マルチクラスを使用する事のメリットの方が大きいので基本的にはマルチクラスで記載していますが、必要のない部分にはマルチクラスをなるべく排除したいところです。
ですので、以下のルールを設けます。
- 異なるComponentどおしのマルチクラスは禁止。
- 異なるProjectどおしのマルチクラスは禁止。
ComponentとProjectどおしのマルチクラスは可能です。
ModifierとUtilityによるマルチクラスは可能です。
ただ、BEMでclassを管理していればそのような事が起こる事は少ないので、あまり意識する必要はありません。
ルールを無駄に増やるのもよくありませんので、頭の隅に置いておくくらいで良いでしょう。
参考記事:CSSのマルチクラス設計の問題点
実際のデザインを再現しながら実装
決めたルールをともに実際にcssを組んでみます。
以下のデザインを制作します。
Componentの作り方
Projectを制作するよりも先にComponentを制作します。
Componentは最小限のパーツですので、まず、今回のデザインからComponentに当たるパーツを抜き出します。
すると以下の5つのパーツと画像が上がりますので、こちらを制作しましょう。
html
<img class="c-image -size_cover" src="http://placehold.jp/24/1571DA/ffffff/320x200.png"></div> <div class="c-tag -size_S -corner_orange" href="#">New</div> <div class="c-sentence -size_20_18 -b_b -color_base">記事タイトル</div> <div class="c-sentence -size_16_14 -color_base">記事の説明文が入ります。ダミー文章。...</div> <div class="c-sentence -size_12 -color_gray">2020/1/1</div>
scss
//画像 .c-image { width: 100%; height: 100%; background-color: #ccc; position: relative; &.-size_cover { object-fit: cover; } } //文字系 .c-sentence { width: 100%; text-decoration: none; &.-size_20_18 { font-size: 20px; //スマホサイズ //font-size: 18px; } &.-size_16_14 { font-size: 16px; //スマホサイズ //font-size: 16px; } &.-size_12 { font-size: 12px; } &.-color_base { color: $baseColor; } &.-color_gray { color: $baseColorLv3; } &.-b_b { font-weight: bold; } &.-right { text-align: right; } } //タグ .c-tag { display: inline-block; text-decoration: none; &.-size_S { padding: 3px 8px; font-size: 12px; } &.-corner_orange { color: #fff; font-weight: bold; background-color: $accentColor; &.-size_S { padding: 5px 8px; } } }
サイズ系と色系のmodifierを分けて制作しているところがポイントです。
サイズと色は派生が起きやすいので分けておくと便利です。
Componentを制作し、デザインに見立ててこちらをただ並べると以下のような見た目になります。
ものがただ縦にならんでいるだけですので、これらConponentパーツに肉付けをし、適切な位置に配置します。
Projectの作り方
Componentができたら次にProjectを制作します。
ProjectはComponentの位置を決めるものです。イメージとしては、骨格です。
Projectで作った骨組みの中にComponentを配置していくイメージでProjectとComponentを組み合わせてパネルを一枚制作します。
html
<a class="p-link-panel -card" href="#"> <div class="p-link-panel__new c-tag -size_S -corner_orange" href="#">New</div> <div class="p-link-panel__img"><img class="c-image -size_cover" src="http://placehold.jp/24/1571DA/ffffff/320x200.png"></div> <div class="p-link-panel__content"> <div class="p-link-panel__title c-sentence -size_20_18 -b_b -color_base">記事タイトル</div> <div class="p-link-panel__desc c-sentence -size_16_14 -color_base">記事の説明文が入ります。ダミー文章。記事の説明文が入ります。ダミー文章。記事の説明文が入ります。ダミー文章</div> <div class="p-link-panel__sub-info"> <div class="p-link-panel__info-item c-sentence -size_12 -color_gray">2020/1/1</div> <div class="p-link-panel__info-item c-sentence -size_12 -color_gray">123View</div> </div> </div> </a>
scss
.p-link-panel { position: relative; text-decoration: none; &.-card { width: 100%; display: block; & .p-link-panel__title { margin-top: 8px; } & .p-link-panel__desc { margin-top: 8px; } & .p-link-panel__content { padding: 24px 16px; display: block; border: solid 1px #ccc; text-decoration: none; } } } .p-link-panel__new{ position: absolute; top: 0; right: 0; z-index: 1; } .p-link-panel__img { width: 100%; height: auto; } .p-link-panel__sub-info { text-align: right; } .p-link-panel__info-item { display: inline; & + & { margin-left: 10px; } }
ProjectとComponentを組み合わせて作ることできちんとデザインを再現する事がで来ました。
今のところ、このようにProjectとComponentを使用して制作する事で得られるメリットを感じる事はあまりありませんが、ここからいろいろな派生形のデザインを制作するとなった場合に、とても恩恵を受ける事ができます。
ここで制作したhtmlとscssコードを元に派生形を作ってみました。
このように1つのベースを元に多くのデザインを制作する事が可能で、後からデザインを変更する事に強いcss設計を行う事が可能になります。
まとめ
このページで出てきた覚えておくべき事やcssを書く上でのルールをリスト形式に纏めます。
いいcss設計とは
- 予測できる
- 再利用できる
- 保守できる
- 拡張できる
Component、Project、Utilityを意識する
- Component → 意味を持つ最小単位のパーツ。marginなど、位置を決めるスタイルをblockに記載してはいけない。
- Project → Componentを集めてできたパーツ。Compontnの配置を管理するもの。
- Utiltiy → 微妙なスタイルの調整を行う。一行のcssで済むものを記載する。
識別子、プレフィックスを付ける
- Layout => l-***
- Project => p-***
- Component => c-***
- Utility => u-***
- Icon => i-***
- Java Script => js-***
class名の付け方はBEMを使用する
- class名の書き方 → block__element–modifier
- block:いくつかの要素を囲う親
- element:blockのなかの子要素
- modifier:派生形を作りたい場合、これ付けて区別する
- ハイフン繋ぎは一回まで
- modifierを付ける際は先頭の重複部分を省き、『-』ハイフンから始める
Componentから制作する
- Component → Project ===> 〇
- Project → Component ===> ×
ネストに関して
- ネストは基本してはいけない
- WordPressエディタ対応やブラウザ対応の場合など、あえてスコープを切る必要がある場合はネストをしてもよい
プロパティを分けて考える
- 構造…background-image,display,overflow.変数の定義など
- スキン…color,background,animetion,borderなど
- ポジション…margin,position,gridなど
- サイズ…font-size,with,paddingなど
プロパティの継承に関して
- 異なるblock間でのスタイルの継承の禁止
モディファイアの影響範囲に関して
- モディファイアは一番親のclassにつけて、elementのスタイルを親のモディファイアが全て管理する。
- ProjectとComponentで互いにモディファイアの影響をあたえる事は禁止。
マルチクラスに関して
- 異なるComponentどおしのマルチクラスは禁止。
- 異なるProjectどおしのマルチクラスは禁止。
あくまでも参考にすぎない
今回詳細したcss設計やルール書き方はあくまでも参考の一部にすぎません。
このcss設計は私がtoBサイトを制作する事が多く、規則正しいパーツが多いデザインに適したやり方であって、もっとアート性の強いデザインや、ベンチャー系、toC系には不向きな場合があります。
1つの意見だけでなく多くの情報を取り入れその場に適した技術を採用する事が正しいです。
適した技術の採用には職場の先輩や、同じような案件を沢山こなしている先輩に伺いましょう。