あなたの知らない CSS ベストプラクティス

(S)CSS Best Practices That You Have Not Yet Known

translated on

React 、Angular 、Vue.js などの一般的なフレームワークを使用してアプリケーションを構築している人にも、スタイルの追加は必要です。使用するテクノロジーによっては、スタイルを特定の記述方法で書くことが求められるからです。たとえば React なら、コンポーネントの性質上、CSS Modules を使ってスタイルを記述する方が良いでしょう。新しい CSS の機能を使いたいのであれば、 CSSNext をおすすめします。Sass や LESS のような、古き良き CSS プリプロセッサのことも忘れてはいけません。あなたは、こう思っているかもしれませんね。ツールの数だけ記述方法が存在するに違いない・・・。そうですね、その通りです。でも、基本は同じなんですよ。

この記事では、CSS Modules や Sass / LESS を使用するかどうかにかかわらず、堅牢かつメンテナンス可能な CSS を書くためのヒントをいくつか紹介したいと思います。なお、ヒントは SCSS 構文で表記しますので、 Sass を導入したいのであれば、このガイドから始めると良いでしょう。この基本概念は、他の CSS ツールやプリプロセッサでも簡単に導入することができます。CSS Next で拡張した、プレーンな CSS を使っている人にとっても、この記事は役に立つと思います。

スタイルの構成

洗練されたフォルダ構造は、プロジェクトのメンテナンスに役立ちますが、それはスタイルにおいても同様です。重要なのは、参照先に応じてコードを分割するということです。すべてのルールをひとつの大きな styles.scss で管理すれば、間違いなく問題が発生します。特に、プロジェクトをスケールアップして、新しいコンポーネントを追加したい場合は、要注意です。

プロジェクトのスケーリングという観点から見れば、スタイルの構造を適切に設定することが不可欠になります。プロジェクトで使用するコンポーネントに応じて、ファイルを整理してくれる構造を作るように意識しましょう。テーマのスタイルと、コンポーネントのスタイルは区別したほうがいいでしょう。役割に応じて、ミックスインと変数を、すべてひとつのフォルダにまとめるのもいいですね。

以下は、フォルダ構造の一例です。

├── /base
│  ├── _core.scss
│  ├── _colors.scss
│  ├── _settings.scss
│  └── _typography.scss
├── /utils
│  ├── _animations.scss
│  ├── _easings.scss
│  ├── _grid.scss
│  └── _misc.scss
├── /components
│  ├── _header.scss
│  ├── _footer.scss
│  ├── _button.scss
│  └── _sidebar.scss
└── main.scss

main.scss では、こんな感じです。

// *** Vendor ***
@import 'sanitize.css';

// *** Settings ***
@import './base/settings';
@import './base/colors';

// *** Utils ***
@import './utils/easings';
@import './utils/animations';
@import './utils/grid';
@import './utils/ui';

// *** Base ***
@import './base/core';
@import './base/typography';

// *** Components ***
@import './components/header';
@import './components/footer';
@import './components/button';
@import './components/sidebar';

色に名前をつける

さて、あなたは Web サイトのデザイン案を持っていて、コーディングのレイアウトから始めたいと思っているところです。プロジェクトの色を管理するために、賢い人は何をするのでしょう?単純なのは、スタイルルールで色の値を直接書き込むやり方です。しかし、この方法は将来、面倒になる可能性があります。しかし、幸いなことに、CSS と Sass はどちらも変数が使えるのです。

Sass では、任意の値を変数として保存することができますから、色の値を変数として保存しておくことは理にかなっています。多くの場合、色の値の定義には HEX(16進数)が使われています。HEX はコンピューター用に設計された形式なので、人間にとってはまるで暗号のようです。#7fffd4#ffd700 などと書いてあるのを見て、どんな色なのか解読するのは到底不可能です(解読できる人は、 CAPTCHA テストで生体認証に失敗するかもしれませんね)。ひとつめの色はアクアマリン、ふたつめの色はゴールドです。こうやって色に名前を付けていったほうが、簡単ですよね?

Web デザインにおいては、特別な役割を持っている色があります。ひとつはブランドのカラー、もうひとつは背景色です。ブラウザ用にコードを記述するだけではなく、その特別な色のために、堅牢な構造を作っておく必要があるのです。デザイナーの助けを借りるのもいいでしょう。その人が色の再現に及び腰なら、あなた自身でやるという選択肢もあります。

まずは、色に名前を付けることから始めます。HEX の値を人間が解読可能な名前に変換するのは無理な注文ですが、幸い、最適なツールがあります。私は Name that Color というサイトを使っていますが、好みのものを使えばいいでしょう。

色に名前がついていれば、役割を割り当てることができます。 primary に使用する色と、 secondary に使用する色を定義するといいでしょう。どうやって色の役割を設定するかわからなければ、 Bootstrap のカラースキームが役に立つと思います。

これが、色設定の一例です。

// Names
$white: #fff;
$black: #000;

$aquamarine: #7fffd4;
$alabaster: #f9f9f9;
$alto: #d9d9d9;
$mine-shaft: #333;

// Roles
$primary: $white;
$secondary: $alabaster;
$accent: $aquamarine;

$text: $mine-shaft;
$border: $alto;

ネストされたセレクタをフラットにする

CSS プリプロセッサは、ツールセットに便利な機能を追加するだけでなく、コードの整理にも一役買ってくれます。最も良い例は、スタイル宣言のネストです。Sass は、セレクタを他のセレクタにネストすることができるので、相関関係を見られるようになります。非常に強力な機能なので、ともすれば使いすぎてしまうかもしれません。

階層は、3つ以上深くしないことをおすすめします。このルールは、セレクタの詳細度と、ネストされたセレクタどちらにも当てはまります。この限界を超えてしまうと、セレクタの詳細度が増すだけでなく、コードが読みにくくなってしまうのです。

以下、例を見てみましょう。

// Bad
.article {
  .title {
    color: $accent;
  }

  .text {
    color: $text;

    .img {
      width: 100%;
    }

    @media only screen and (min-width: 720px) {
      .text {
        .img {
          width: 300px;
          height: auto;
          margin: 0 auto;
        }
      }
    }
  }
}

// Good
.article {
  .title {
    color: $accent;
  }

  .text .img {
    width: 100%;
  }
}

@media only screen and (min-width: 720px) {
  .article {
    .text .img {
      width: 300px;
      height: auto;
      margin: 0 auto;
    }
  }
}

親参照をしすぎない

Sass では、親セレクタを参照することができます。ネストと組み合わせて、親セレクタを必要とするルールの定義が可能です。たとえば、こんな感じです:

.link {
  &:hover {
    color: $accent;
  }

  &::after {
    display: inline-block;
    content: '\0362';
    vertical-align: middle;
  }
}

& の使用は、ときどきミスの元となります。これは、セレクタの悪い使用例です:

.text {
  // .article .text に変換される
  .article & {
    color: $text;
  }

  // .text + .text に変換される
  & + & {
    margin-top: 15px;
  }
}

ご覧の通り、アンパサンドセレクタのせいで、スタイルは無駄に複雑になり、コードが読みにくくなってしまいました。

プロパティの記述順には意味を持たせる

プロパティのソートは必須ではありませんが、したほうがいいです。プロパティが適切にソートされているということは、コードスタイルが一貫していることと同義だからです。そのうえ、コードをスキャンするときに便利になります。プロパティのソートにおいては、信頼できる情報源は存在しないので、アルファベット順など、好きな設定で並べ替えることができます。私は SMACSS の設計概念の書式ガイドラインに載っているルールを少し変更して使っています。これは、私のおすすめのソート順です。

  • Box(position、display、width、margin など)
  • Text
  • Background
  • Border
  • その他(アルファベット順)
// Before
.action-button {
  height: 30px;
  z-index: 3;
  background-color: $accent;
  position: fixed;
  width: 30px;
  color: $white;
  bottom: 15px;
  right: 15px;
}

// After
.action-button {
  position: fixed;
  right: 15px;
  bottom: 15px;
  z-index: 3;
  width: 30px;
  height: 30px;
  color: $white;
  background-color: $accent;
}

クラスの命名規則を使う

プロジェクトが大きくなってくると、スタイルが汚くなりがちです。特に、既存のルールを拡張する必要があるときはなおさらです。残念ながら、CSS は名前空間の衝突を避けるためのメカニズムを持っていません。言い方を変えれば、CSS で書かれたスタイルはすべてグローバルなので、意図せずプロパティを上書きしてしまう可能性が非常に高いのです。

CSS はカスケードの性質を持っているので、特殊なセレクタを定義することが重要になってきます。特殊なセレクタを使えと言っているのではありません。特定の要素にスタイルを追加する場合、タグセレクタなどの一般的なセレクタの使用を避けるということです。

これは一例です。

// Bad
.menu {
  li {
    display: inline-block;
    padding: 0 20px;
    border-bottom: 1px solid $accent;
  }

  ul li {
    padding: 0 10px;
    border-bottom: 0; // Unnecessary border reset
  }
}

// Good
.menu-item {
  display: inline-block;
  padding: 0 20px;
  border-bottom: 1px solid $accent;
}

.nested-menu-item {
  padding: 0 10px;
}

BEMOOCSSSMACSS のような、一般的なクラス命名規則を使うことができれば、メンテナンス可能なスタイルを書くのは、そう難しいことではありません。私は BEM が好きなのですが、これは Yandex が考えた、とても人気のある方法論です。Block、Element、Modifier の頭文字を取って、このように名付けられています。 BEM を知らない人は、このドキュメントを読むことをおすすめします。

これは、私たちが BEM を使用して作った一例です。

.menu {
  &__item { // it compiles to .menu__item
    display: inline-block;
    padding: 0 20px;
    border-bottom: 1px solid $accent;
  }
}

.submenu {
  &__item {
    padding: 0 10px;
  }
}

わかりやすいメディアクエリを書く

レスポンシブウェブサイト開発において、メディアクエリは最重要ポイントです。ユーザー側のデバイスに合わせて様々な解像度のスタイルを定義したり、レイアウトを変えたりすることができるのは、メディアクエリのおかげだからです。CSS において、メディアクエリは一連のルールであり、ユーザー側のブラウザ上でチェックされます。チェックが通ると、メディアクエリブロックで定められたスタイルが、ウェブサイトに適用されます。

メディアの種類と特徴を説明するクエリを利用すれば、さまざまなデバイスをターゲットに設定できます。ルールをたくさん組み合わせてしまうと、メディアクエリはあっという間に複雑になってしまいます。

// target devices with min width of 768px (possibly tablets) and with wide screen
@media only screen and (min-width: 768px) and (max-device-aspect-ratio: 9 / 16) {
  body {
    font-size: 20px;
  }
}

定義を変数化することで、メディアクエリの作成は、より開発者フレンドリーになります。Sass では、インターポレーションの #{} を使えば、文字列を通常のCSSコードとして使用することができます。

$medium: 768px;
$screen-medium-wide: 'only screen and (min-width: #{$medium}) and (max-device-aspect-ratio: 9 / 16)';

@media #{$screen-medium-wide} {
  body {
    font-size: 20px;
  }
}

他の at-rule でも、同じワザが使えます。複雑な @supports ルールを定義するときに便利です。

モジュラーなスタイル

CSS には、便利に使えるセレクタがあります。たいてい、クラスセレクタか子孫セレクタを使うことができます。また、モジュラー形式の記述を記述するのにぴったりなセレクタが、もうひとつあります。プラスセレクタの名前でも知られている、隣接兄弟セレクタです。

セレクタの要素に先行する要素があるときにのみ適用されるスタイルを記述したいとします。たとえば、記事のテキストにタイトルが付いているとしたら、そこへ余白を追加することができます。やり方は次の通りです。

.article {
  &__title {
    color: $accent;
  }

  // Applies margin only, when .article__title is present
  &__title + &__text {
    margin-top: 15px;
  }

  &__text {
    color: $text;
  }
}

とても便利なスタイルの定義方法で、私も気に入っています。しかし、マイナスポイントもあります。まず、セレクタのパフォーマンス性は、それほど高いとは言えません。セレクタに、モジュール性よりもパフォーマンスを期待しているのであれば、おすすめはしません。次に、兄弟セレクタを使用すると詳細度が高くなるので、後からスタイルを上書きすることは難しいかもしれません。とはいえ、要素の存在に応じてスタイルを適用するのであれば、試してみる価値はあります。

参考文献

さて、あなたはもう、読みやすく、メンテナンスもしやすいモジュラー形式を記述できるようになりましたね。より自信を持ってスタイルを書けるようになるために、もっとアドバイスが欲しいというのであれば、次の記事をチェックしてください。