新卒社員が本気でengineerを目指してみた

エンジニアリング未経験の新卒社員が本気でエンジニアを目指すための勉強記録

設計を学んでなんとなくCSSを書くのを卒業してみたい【Part1】

概要

なんとなくCSSを書いていた初心者の私がフロントエンドエンジニアとして設計から考えてCSSを書くために学んだことをまとめてみた。

CSS設計の重要性

Webは構造そのものを大きく変更したり、予想外の要素を差し込む・差し引く可能性がある。この事態は誰にも予想はできないが、どのように変化に対応しても全く手を入れる必要がないCSSを書くのは不可能。 だからいかにしてメンテナンス効率を下げずに、また少ない工数で回収ができるようにするための設計が重要。

よりよいCSSとは以下が重要

  • 予測しやすい 期待通りに振る舞うかどうか。他のルールが影響して、記述した通りにならない・追加したルールが他のルールに影響を与えることがないようにすること。

  • 再利用しやすい CSSがコピペで膨大にならないようにする。そのためには抽象的で機能ごとに分離されている必要がある。UIデザインに対してマークアップCSSを一度作れば、再度同じようなUIに遭遇したときにわざわざ書き直す必要がない。

  • 保守しやすい 追加したルールによって既存ルールを壊さないために、新しいルールを追加するときに、既存ルールのリファクタを必要としないこと。 ・拡張しやすい プロジェクトに関わる自分と自分以外がメンテ・管理しやすくする。この拡張しやすいとは、サイトの規模が大きくなるにつれて、拡張しやすいようになっているものかどうか。

コンポーネント設計

コンポーネント設計とは、CSSの再利用性や保守性を高めるための機能や振る舞いなどを明確に分離すること。 あるコンポーネントの変更が、別のコンポーネントを壊してしまうといったことを防ぎ分離が成立させる。 その結果、メンテナンス性が高まり、また他のコンポーネントとの不要な依存関係をなくすことで再利用性も高まる。

OOCSSとは

OOCSSの中でのオブジェクトは、繰り返されるビジュアルパターンを指し、1つのまとまったHTML, CSS, jsで書かれた独立したスニペットとして再利用できるもの。 OOCSSの原則は

構造と見た目の分離

コンポーネントの基本構造と、具体的なルール・機能を分離すること

f:id:shorugby14:20191016000401p:plain
(引用: https://www.kaqiita.com/entry/2019/02/11/074006)

//alert.html
<div class="alert success">成功!</div>
<div class="alert warning">警告!</div>
<div class="alert error">エラー!</div>
//alert.css
.alert {
  border: 2px solid #333;
  border-radius: 6px;
  padding: 10px;
  font-size: 16px;
  color: #333;
}

.success {
  border-color: #494;
  background-color: rgba(68, 153, 68, 0.3);
}

.warning {
  border-color: #DD0;
  background-color: rgba(221, 221, 0, 0.3);
}

.error {
  border-color: #C44;
  background-color: rgba(204, 68, 68, 0.3);
}

こうすることによる.alertで基本的な体裁・構造を作っておき、基本構造を別ルールとして分離させておく。そして、バリエーションとして存在する色を別のルールとしてまとめる。こうしてclass属性に複数の値を持たせて任意のアラートメッセージを作る。 こうすることでコードも少なくなり、基本デザインが変わっても.alertだけの微小な修正で済む。新しいバリエーションが増えた場合も、新しいルールを追加するだけで済む。

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

場所に依存しないセレクタを書くということ。 サイトのロゴがあった場合に、ヘッダーに最初に使っていたとする。

#header .logo {
    display: inline-block;
    width: 200px;
    height: 80px;
}

しかしフッターにも同じロゴを追加したいときは

#footer .logo {
    display: inline-block;
    width: 200px;
    height: 80px;
}

を追加しないとならない。 これならば、.logoという部品として単純に定義することができ、また小さいロゴが欲しいとなったときも対応できる。

.logo {
    display: inline-block;
    width: 200px;
    height: 80px;
}
.small-logo {
    width: 140px;
    height: 56px;
}
//html
<h1 class=“logo logo-samall">CSS</h1>

場所に依存しないことで、他に置くこが必要になったとしても大きな修正は不要となる。

SMACSS

SMACSSは場所に依存したセレクタではなくコンポーネントを拡張していくアプローチの設計など、OOCSSをより実践的に扱うためのガイドラインの1つ。 SMACSSの特徴は以下。

カテゴライズ

ページに存在する多くのパターンを、CSSのルールを5種類にカテゴライズした上でそれぞれの考え方や記述ルールを取り決めている。

Base

近年のCSSの開発でreset.cssやnormalize.css, sanitize.cssなど要素のスタイルを初期化するライブラリを採用することが増えている。全体の背景やフォントファミリーなど基本的にこのカテゴリで定義した、よほどの理由がない限り書き換えることがないものなど、要素そのもののデフォルトスタイルを定義していること。

Layout

IDを用いて定義するようなヘッダーやフッター、コンテンツエリアやサイドバーなど構成の大枠やセクションを作る要素へのルール。 IDセレクタは詳細度を高めるが、レイアウトのような大きなセクションはページごとに1つしか存在しないため許容範囲といえる。しかし、場所に依存するセレクタは書かないように注意する。 またグリッドレイアウトルールのようなものもSMACSSではLayoutに含んでおり、クラス名の頭にl-*(Layoutの頭文字)をつけるような命名規則もある。

#header, #footer {
    hoge
}

#sidebar {
    hoge
}

/* 場所に依存するセレクタなのでダメ! */
#sidebar .menu {
    hoge
}

/* l-*(Layoutの頭文字)をつける命名規則 */
.l-grid {
    hoge
}
Module

Layoutパターンを除く、ページを構成するほぼ全てがこのカテゴリに属する。ボタンや見出し、リンクやアラートメッセージなど再利用できるオブジェクト単位でパーツのスタイルを定義すること。

State

jsの制御により切り替わるような、状態を表すルール。要素を隠すものや、エラー表示のものなどが一例。 クラス名の頭にls-*をつけるような命名規則もある。

.is-hidden {
    display: none;
}

.is-error {
    color: indianred;
}

見た目が変わるものは、モジュール名をつけることで汚染を防ぐ。

.tab {
    display: inline-block
    Background-color: hoge1;
    color: hoge2
}

.is-tab-active {
    Background-color: hoge3;
    color: hoge4
}

/* このような.is-active単独のルールは設けないようにする */
.is-active {
    font-weight: bold;
}

SMACSSはStateには!importantの使用を推奨している。Stateは必然的にそのスタイルに変更することが目的なので、詳細度の高い!importantを使うことは許容範囲になる。 コンポーネントの状態を制御する意味ではメディアクエリもそうなので、メンテナンスを考えるとメディアクエリのルールのコンポーネントののスタイルに置くのがメンテナンス性は高い。

.l-grid > li {
    float: left;
}

@media screen and (max-width: 400px) {
    .l-grid {
        float: none;
    }
}

メディアクエリが一箇所に集約されない分、宣言する箇所は増えるがコンポーネント単位での設計を考えた場合はこれが正といえる。

Theme

サイト全体の見た目の雰囲気を統一させるための定義をする。いわゆるテーマを切り替えるような機能が求められるCSS。 Theme-*接頭辞の命名規則がある。

/* theme/sea.css */
.theme-background {
    background-color: aqua;
}

.thema-border {
    border: 1px solid blue;
}
//html
<div class="theme-backgroud">
    <div id="header" class="theme-border"></div>
    <div id=“sidebar" class="theme-border"></div>
</div>

このような設計であれば、どの要素に対してもテーマのスタイルが定義されるのが明確になる。

BEM

命名規則での設計アプローチ。BEMはBlock, Element, Modifierの略。

Block

入力フォームやアラート、ヘッダーやフッターなどの大きなセクションのこと。

.alert {
    hoge
}
Element

Blockを構成する要素。アラートメッセージの例だと、 それを構成する.alert-titleや.alert-bodyがElement。 BEMの命名規則だとalertというBlockに対する、titleというElementということで__を使う。

.alert__title{
    hoge
}
Modifier

BlockまたはElementのバリエーション違いの要素。アラートメッセージの警告用のスタイルのバリエーションだったり、SMACCSのStateカテゴリのような状態の違いも含む。 Modifierの場合は_一つで区切る。

.alert_warning {
    hoge
}
//html.slim
.alert.alert_warning
    h2.alert__title
        | hoge

コンポーネントを構成する要素に対してそのコンポーネント名を名前空間として扱いクラス名の接頭辞にすることで、どれがコンポーネントを構成する要素であるかを明らかにでき、他スタイルへの汚染を防ぐことができる。またどのクラスがどのコンポーネントのどういった機能かもDOMからわかることができる。

MindBEMding

ElementをBlockの親子とし__で区切り、Modifierをバリエーションの違いとして対等を評価し—で区切る。 またニコラス・ギャラガーが開発した、suitCSSでは単語の区切りをアッパーキャメルケースを採用しBlockの名前の頭を大文字にしている。Elementgはアンスコではなくハイフン1つ、Stateパターンはis-命名規則を採用。 また、Utilityと呼んでいる汎用的な機能はu-を採用している。

MCSS

Multilayer CSSの略でレイヤー間の依存関係を明確にすることでスムーズなコンポーネント化を進めることができる。

基本原則

コンポーネントの持つスタイルを1つのセクションの中でまとめられる。BEMでいうBlock, Element, Modifierがまとまて同じ箇所に記述し、メンテナンス対象を絞り込むことができる。 Elementの記述方法はBEMと同じだが、ModifierはBlock名を除き__ではじまる命名で、Blockのクラスと結合したセレクタにする。

.alert {
    hoge
}
.alert.__warning {
    hoge
}

.での結合はIE6では正しく解釈されないので注意。SMACCSのStateで!importantを許容したように、Blockである.alertのルールの拡張・変更が目的であれば確実なので許容される。 __modifier単体のセレクタのルールを記述すると、いずれかのコンポーネントのスタイルを汚染する可能性があるから注意。

Foundation

SMACCSでいうBaseカテゴリと同様。プロジェクトの土台として、reset.cssなどを用いて各要素を初期化する。よって最初に読み込みをする必要がある。

Base

プロジェクトの各所で再利用でき、抽象的な要素。ボタンやフォームなどが該当し、他のレイヤーから用意に拡張・変更ができるようなものである必要がある。 Baseレイヤーからのカスケーディングルール ・BaseレイヤーコンポーネントからBaseレイヤーコンポーネントを上書きできる。 ・BaseレイヤーコンポーネントからProjectレイヤーコンポーネントを上書きできない(下層レイヤーからの上書きを禁止する)。

Project

Baseレイヤーコンポーネントよりも具体的なページを構成する要素。Projectレイヤーコンポーネント内でBaseレイヤーコンポーネントを用いる場合、上書きすることができる。

//html.slim
ul.comment-list.block-list
    li.block-list_item
        | hoge1
    li.block-list_item
        | hoge2
//BaseLayer
.block-list {hoge}
    .block-list_item {
        border-bottom: 1px
        padding: 0.3em 1em
    }

//ProjectLayer
.comment-list {hoge}
    .comment-list .block-list_item {
        padding: 0.5em 1em
    }

Projectレイヤーにおけるカスケーディングルール ・Projectレイヤーコンポーネント内でBaseレイヤーコンポーネントを上書きすることができる。 ・Projectレイヤーコンポーネント内でProjectレイヤーコンポーネントを上書きすることができる。

Cosmetic

下層のレイヤーコンポーネントには含まれないような、ちょっとしたスタイルが含まれる。リンクカラー、少数のプロパティで構成される.font-sizeXLのようなクラス、グローバルなModifierが例。 部分的に他のリンクカラーと違う色を使いたいときや、部分的にフォントサイズやマージンを与えるものや、要素を隠すためのdisplay: noneを持つクラスなども含まれる。

//html.slim
.login_submit
    Button.login_button.btn
        | ログイン
    = link_to ‘パスワード忘れましたか?’, class: ‘al’
// FoundationLayer
a {
    color: blue;
}

//Cosmetic
.al {
    color: red;
}

.font-sizeXL {
    font-size: 48px;
}
.hidden {
    display: none
}

Cosmeticレイヤーにおけるカスケーディングルール ・Cosmeticレイヤー自身を含め、例外のコンテキストレイヤー以外のレイヤーからは上書きできない Contextとは特定のブラウザやログイン中のときなど、メディアクエリによる特定条件下にある場合に例外的なスタイルを用意するためのレイヤー。

.button {hoge}
    .touch .button {
        lone-height: 44px;
    }

@media screen and (max-width: 320px) {
    .button {
        padding: 0;
        width: 100%;
    }
}

FLOCSS

OOCSSやSMACCS, BEMの命名やMCSSのレイヤー設計のアイデアを取り入れている。

基本原則

Foundation Layout Object(Component, Project, Utility) で構成されている。

Foundation

reset.cssなどを用いて各要素を初期化したり、プロジェクトの基本スタイルを定義する。

Layout

ヘッダーやメインのコンテンツエリアなどプロジェクト共通のコンテナーブロックのスタイルを定義する。基本的にページ単位で1つなのでIDセレクタ使用が推奨。

Object

FLOCCSでは基本的に全てをコンポーネントのように扱う。その中でレイヤーの概念を持ち、上書きのルールを定めている。

Component

汎用性が高く、再利用性が高いものを定義する。最低限の昨日を持ったものとして定義されるべきで、それ自体が固有の幅や色を持つことは避けるべき。

Project

いくつかのComponentとそれに該当しない要素によって定義する。MCSSのProjectと同様で記事一覧や画像ギャラリーなどコンテンツを構成する具体的な要素が該当する。 ページを構成する要素のほとんどがこのレイヤーのコンポーネントに該当する。

Utility

ComponentとProjectレイヤーのModifierで解決が難しい・わずかなスタイル調整のための便利なクラスなどを定義する。 MCSSのCosmeticレイヤーに近い。Object自体が持つべきでないmarginの代わりに.u-mbs{margin-bottom: 10px;}のようなUtilityObjectを用いて隣接するコンポーネントとの間隔を作るといった役割がある。

命名規則

役割を以下のように明確にし、クラス名から役割がわかるようにする。 Component: .c- Project: .p- Utility: .u-*

カスケーディング

後ろのレイヤーほど具体的でカスケードが強いルールじゃないといけない。 原則として、レイヤー間のカスケーディング、他のコンポーネントを親とするセレクタを用いたカスケーディングは禁止。

// Component
.c-button {
  ...
}
.c-dialog .c-button {
  ...
}
 
// Project
.p-comments {
  ...
}
.p-articles .p-comments {
  ...
}