はじめに
業務でCSSを書くようになってから、いくつかの月日が流れました。
CSSを学び始めた当初は、要素をキレイに横並びにすることすら手こずっていましたが、最近は随分スムーズにデザイン通りのスタイルを書くことができるようになりました。
今日に至るまで、過去の自分が書いたCSSへの後悔の念で眠れない日々や、原因のよくわからない表示崩れの悪夢にうなされる夜もありました。1
これからCSSを学ぶ人、CSSにはあまり詳しくないけどたまに書くよという人にそんな思いをして欲しくない。できたらCSSのことを好きになって欲しい。
そんな思いで自分がスタイルを書く時・レビューをする時に気をつけていることを(自戒も込めて)まとめまてみました。
🤔 良いスタイルってなんだろう?
スタイルを書く時に大切だと考えていることは3点あります。
- 開発効率
- デザイン再現性
- パフォーマンス
開発効率
色々な記事や本でも引用されているCSS Architectureという記事によると、CSS設計のゴールには4つのポイントがあります。
- Predictable(予測しやすい)
- Reusable(再利用しやすい)
- Maintainable(保守しやすい)
- Scalable(拡張しやすい)
Predictable(予測しやすい)
予測しやすいスタイルとは、ルールが期待どおりの振る舞いをするスタイルのことです。
ルールを追加や更新した場合に意図しない箇所へ影響を与えないこと、つまり影響範囲が明確であることを指します。
Reusable(再利用しやすい)
再利用しやすいスタイルとは、同じようなデザインのUIが登場した時に再コーディングせず、既存のパーツを使いまわせるスタイルのことです。
抽象的で機能が分離している必要があり、そのようなスタイルは制作・運用を効率的にします。
逆に再利用しにくいスタイルの例をあげると、モジュール自体に他のモジュールに影響を与えるmarginがついていたり、クラス名にh1とついていて、同じ見た目のh2が登場した時に使いまわせないスタイルが挙げられます。
Maintainable(保守しやすい)
保守しやすいスタイルとは、新しいコンポーネントの追加・更新・再配置する場合に既存のスタイルをリファクタリングする必要がないスタイルのことです。
Scalable(拡張しやすい)
拡張しやすいスタイルとは、単純に個々のCSSのルールが増える場合だけでなく、サイト・サービス自体が拡大して、複雑になってきても耐えうることを指します。
サイト・サービスが拡大してチームに新しいメンバーが増えた場合の教育コストが低いことも含まれます。
Scalable(拡張しやすい)スタイルにするためには...
「Predictable(予測しやすい)」、「Reusable(再利用しやすい)」、「Maintainable(保守しやすい)」を守ると同時に、コンポーネント設計により関心の分離・カプセル化を行うことが重要だと考えています。
そのためには、styled-componentsやEmotionなどのCSS in JSを使用してコンポーネント思考でスタイルを書くこと、BEM、OOCSS、SMACSS、FLOCSSといったCSS設計手法を取り入れる事が大切です。
デザイン再現性
デザイン通りの表示になるように、きちんとスタイルが当てられているかを指します。
「デザイン通りの表示」とは、細かくみていくと多くの確認箇所があります。
例えば、下記の場合にも表示が崩れずに、デザインの意図がきちんと反映されているが重要です。
- すべての対応ブラウザで表示した時
- ウィンドウサイズを変えた時
- ウィンドウの縦長/横長表示を変えた時
- 動的に取得する箇所に様々なパターンのデータが入ってきた時
- ブラウザの機能でテキストサイズを大きくした時
- ページを拡大・縮小表示した時
どこまで対応するかの線引きはプロジェクトによって様々かと思います。プロジェクト内で他のメンバーやクライアントと対応範囲の合意を取る事でスムーズに案件を進める事ができます。
パフォーマンス
DOMを必要以上に深くしない
スタイルの計算では、マッチングするセレクタを総当たりで探します。
DOMが多くなれば、それだけ計算コストがかかります。
なるべくシンプルなDOM構造で書くことを意識しましょう。
セレクタを最適化する
ブラウザはセレクタを右から左へ照合する
.box p {...}
というセレクタの指定があった場合、ブラウザはページ内のすべてのpタグを探し、次にclass="box"
をもつ要素を探します。
そのため、キーセレクタ(一番右のセレクタ)とマッチする要素・子孫セレクタが少ないほどパフォーマンスが良いセレクタだと言えます。
要素にはなるべくユニークなクラスを与え、隣接セレクタ(A + B)や子セレクタ(A > B)、全称セレクタ(*)の乱用をしないよう心がけましょう。
ただし、近年のブラウザは以前より処理速度が改善しているため、神経質になりすぎず、開発効率も考慮してセレクタを最適化すると良いでしょう。
不要なスタイルがない
スタイルは必要な箇所に必要なだけ書くことが大切です。
下記を意識することでCSSのサイズが肥大化しないようにしましょう。
- 使用していないスタイルは削除する
- ベンダープレフィックスは必要十分なだけ
- なるべくシンプルなスタイルを書く
レンダリングの負荷が考慮されている
ブラウザがレンダリングする際に要素の位置関係と大きさをを計算するレイアウトの処理後にしてから、ペイントの処理が行われます。
引用: レンダリング パフォーマンス | Web | Google Developers
アニメーションをつける際は、なるべく処理順がフローの後のものを動かす方がレンダリングの負荷は軽くなります。
例えば、要素の位置を動かす時に、top
やleft
の値をいじるより、transform: translate();
を使用する方がパフォーマンス向上には有利です。
各CSSプロパティがどの段階で処理されるのか知りたい場合は
CSS Triggersが詳しいです。
また、will-change
プロパティを使用すると、要素にどのような変更を加えるかを前もってブラウザに知らせることができます。適切に使用することでパフォーマンスの改善が期待できます。
参考: MDN Web Docs | will-change
🤢 スタイルアンチパターン
自分がコードレビューをするときに「mustで直してね」とコメントを書きがちな具体事例を紹介します。
もちろん、最適なスタイルの書き方はプロジェクトの性質によって変わってくる部分もあると思います。
プロジェクトのコーディング規約がない場合やこれから作るという場合の参考になれば幸いです。
!important
を使用している
!important
を指定すると優先度が最強になってしまいます🤯
!important
を指定したスタイルを上書こうとすると、セレクタで詳細度を高めた上で!important
をぶつけるという、血を血で洗う戦いが始まってしまいます。
!important
は切り札にして諸刃の剣...、これが繰り返されると地獄を見る羽目になるのでやめましょう。
ただ、既存のソースをいじれない場合や、外部ライブラリのスタイルを上書きしたいなど、どうしても!important
使わなければならないという場面に出くわすこともあるかもしれません。
その場合は、後世のために「なぜ!important
なしではダメだったのか」をコメントに残すことをオススメします。
idにスタイルを当てている
idは1つのページで1度しか使用できません。
そのためidセレクタを使用すると、再利用することができなくなってしまいます。
また、idセレクタはクラスセレクタより詳細度が高いです。詳細度が複雑になると詳細度の管理コストが発生します。
結果として、予測しづらく、再利用しにくいCSSになってしまいます。
不要なスタイルが存在する
不要なスタイルはCSSのサイズを無意味に増やしてしまうし、見通しも悪くなります。
また、スタイルが継承されることで、その子孫要素にも不要なスタイルの影響が出てしまいます。
それにより、打ち消しのスタイルが増えたり、依存関係がわかりにくくなってしまいます。
不要なスタイルが混入する原因としては、「デザインデータのCSSからコピペしてきた」「色々試してた結果残ったままになっていた」というものが経験上多いです。
期待通りの表示になった後、不要なスタイルが紛れていないか再度確認すると良いでしょう。
value
が0の場合に単位が指定されている
margin
・padding
・border
などを打ち消す時に0
を指定することがありますがCSSの仕様でvalueが0の時は単位がoptionalになります。
とても細かいのですが、不要なものは書かないのが原則なので0の時は単位を書かないようにしましょう。2
line-height
に単位が指定されている
line-height
には単位を使用せず相対値で書く方がよいとされています。
理由は2点あります。
1つ目の理由は、スタイルは親から子に継承されるので単位ありの絶対値で指定すると、子要素で打ち消すスタイルを書かなくてはならない場合があります。
デザインでは「1.2」や「1.5」など、行間が統一されて決められていることが多いため、相対値で指定する方が打ち消さずに済むことが多いです。
2つ目の理由はブラウザの機能で文字を大きくした時に、line-height
の高さより文字の大きさが大きくなって、表示が崩れることがあるためです。
margin
が相殺されている
margin
には相殺の性質があります。
相殺が起きている要素間に別の要素が増えた場合や要素を再配置・再利用する場合にうまくいかないことが多いです。
再利用性・メンテナンス性が低くなってしまう原因になるため、相殺が起きるスタイルはあまり推奨しません。
マージンの相殺 | MDN
🤔 気をつけたいスタイル
「絶対だめ」というわけでもないですが、開発効率やデザイン再現性観点で注意したいスタイル指定についてまとめました。
height
が決め打ちで指定されている
要素のheight
を決め打ちで指定する場合、動的なテキストが長くなる場合がないか・画面サイズが変わって中に入るテキストが折り返す場合がないかに注意しましょう。
また、ブラウザ側でテキストサイズを大きくした時に表示崩れが起きないかも確認すると良いでしょう。
margin
の値がマイナス
値がマイナス値のmargin
はネガティブマージンやマイナスマージンと呼びます。
多用するとmargin
の計算が複雑になり、デザイン崩れの原因になりやすいです。
ネガティブマージンを使わなくても実現する方法がないか考えて、ここぞという時以外にはなるべく使用しないのが吉です。
Flexbox
or Grid Layout
以外での横並び・上下中央寄せ
一行の横並びはFlexbox
、複数行の横並びはGrid Layout
で書くと、行数やDOMの深さが少なく済むことが多いです。
また、Flexbox
or Grid Layout
を使うことでレスポンシブ対応もしやすくなります。
position: absolute;
を指定している要素の直前の親にposition: relative;
の指定がない
position: absolute;
はpositionプロパティのstatic以外の値が指定されている親要素を基準に位置が決まります。基準としたい要素にposition: relative;
を指定して、その直下にposition: absolute;
要素を置くことで依存関係がわかりやすくなり、「あれ?absoluteなアイコンが、気がついたらいないぞ?」ということを防止できます。
👼 オススメスタイルTips
margin
の向きを揃える
上で述べたmargin
の相殺を防止するために、margin
の向きを揃えることはとても有効です。
margin-top/left
で揃えるかmargin-bottom/right
で揃えるかは、個人的にはプロジェクト内で統一されていればどちらでもいいかなと思います。
font-size
の指定はrem
font-size
をpx
で指定すると、ブラウザの機能でテキストサイズを大きくしようとしても変更できないため、アクセシビリティを考慮するとイマイチです。
また、SPとPCで基準となるfont-size
を変えたいという場合やSPの時だけ画面幅に応じてfont-size
を変えたいという場合もあるでしょう。
htmlタグにベースのfont-size
を指定して、各要素にはrem
で指定をするとfont-size
を扱いやすく書くことができます。
html {
font-size: 62.5%;
@media screen and (max-width: 768px) {
font-size: calc(100vw / 375 * 10);
}
}
全要素にbox-sizing: border-box;
を指定する
box-sizing: border-box;
を指定すると、width
とheight
で指定する幅と高さにpadding
とborder
の値が含まれるようになります。
これを全要素に指定することで幅と高さの算出方法をすべてのスタイルで統一する事ができます。
また、デザインデータを作成する際に、ボーダーあり/なしのパターンで高さを揃えるために、要素の内側にボーダーをつける事が多いらしいです。
box-sizing: border-box;
の指定によって、デザインデータからCSSにスタイルを起こしやすくすることもできますね。
ressなどbox-sizing: border-box;
の指定が初めから含まれているリセットCSSを使うか、そうでない場合はベースのスタイルとして自分で追加しておくとベターです。
*,
::before,
::after {
box-sizing: border-box;
}
便利なツールを利用する
人間は不完全な生き物なので、ツールでできることは任せてしまいましょう。
見落としを防ぐだけでなく、実装やレビューの工数を削減にも繋がります。
便利なツールたち
- stylelintでルールを指定する
- stylelint + Prettierでフォーマットする
- Autoprefixerで自動的に過不足ないベンダープレフィックスをつける
- reg-suitでビジュアルリグレッションテストを行い意図しない表示の変更を検知する
👮♂️ 最後に
1つ1つは細かいことですが、その積み重ねが平和なCSSの世界を築いていくと信じています。
私もまだまだ勉強中なので頑張ります。
世界が平和でありますように🤗