モダンCSSリセットを徹底解説、最近のデバイス・モダンブラウザの仕様に対応
Post on:2021年12月9日
最近のデバイス・モダンブラウザの仕様に対応した、モダンCSSリセットを紹介します。スタイル宣言はたったの9個ですが、個々のCSSについて詳しく解説されているので、記事はけっこう長いです。
なぜそうしたのか、なぜそれを使用しないのか、最近の実装にあわせたCSSの役立つテクニックもたくさん解説されています。
My Custom CSS Reset
by Josh W. Comeau
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
他のいろいろなCSSリセットについては、下記をご覧ください。
- はじめに
- CSSリセット
- 1. ボックスサイズのモデル
- 2. デフォルトのマージンを削除
- 3. 高さは%ベースを使用
- 4. アクセシブルなline-heightを追加
- 5. テキストのレンダリングを改善
- 6. メディア要素のデフォルトを改善
- 7. フォームのfontに関するデフォルトを削除
- 8. テキストのオーバーフローを回避
- 9. ルートのスタックコンテキストを作成
- CSSリセット(コピー用)
はじめに
新しいプロジェクトで私がいつも最初にするのは、CSSの荒削りな部分を取り除くことです。これには、機能的でカスタマイズしたベースラインのスタイルを使用します。
これまで長い間、Eric MeyerのCSSリセットを使用していましたが、10年以上も更新されておらず、その間にCSSはいろいろと変わっています。
そのため、最近は自分で作成したCSSリセットを使用しています。このCSSリセットには、ユーザーエクスペリエンスとCSSオーサリングエクスペリエンスの両方を向上させるために私が発見した小さなテクニックがすべて含まれています。
他のCSSリセットと同様に、デザインや美しさに関して一切の妥協はありません。このCSSリセットは目的の美観に関係なく、どんなプロジェクトでも使用できます。
この記事では、私のCSSリセットについて解説したいと思います。それぞれのルールを掘り下げ、その役割と使用する理由を解説します。
CSSリセット
早速ですが、私が使用しているCSSリセットを紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/* 1. より直感的なボックスサイズのモデルを使用 */ *, *::before, *::after { box-sizing: border-box; } /* 2. デフォルトのマージンを削除 */ * { margin: 0; } /* 3. 高さは%ベースを使用 */ html, body { height: 100%; } /* タイポグラフィの微調整 4. アクセシブルなline-heightを追加 5. テキストのレンダリングを改善 */ body { line-height: 1.5; -webkit-font-smoothing: antialiased; } /* 6. メディア要素のデフォルトを改善 */ img, picture, video, canvas, svg { display: block; max-width: 100%; } /* 7. フォームのfontに関するデフォルトを削除 */ input, button, textarea, select { font: inherit; } /* 8. テキストのオーバーフローを回避 */ p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; } /* 9. ルートのスタックコンテキストを作成 */ #root, #__next { isolation: isolate; } |
比較的短いスタイルシートですが、この小さなスタイルシートにはたくさんの内容が詰まっています。さっそく見てみましょう!
メモ
今まで、CSSリセットの主な目的はブラウザ間の一貫性を確保すること、デフォルトのスタイルをすべて取り除くことでした。私のCSSリセットは、この2つを行っていません。
最近のブラウザはレイアウトやスペースに関して大きな違いはありません。ブラウザはCSSの仕様を忠実に実装しており、期待通りの動作をしてくれます。したがって、現在ではそれほど必要ではありません。
また、ブラウザのデフォルトをすべて取り除く必要はないと考えています。たとえば、<em>
タグにfont-style: italic;
を設定したいとします。個々のプロジェクトのスタイルでは常に異なるデザインを採用できますが、一般的なデフォルトを取り除くことには意味がありません。
私のCSSリセットは、CSSリセットの古典的な定義には当てはまらないかもしれませんが、私はその創造的な自由を手にしています。
1. ボックスサイズのモデル
いきなりですが、ここでクイズです!
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<style> .parent { width: 200px; } .box { width: 100%; border: 2px solid hotpink; padding: 20px; } </style> <div class="parent"> <div class="box"></div> </div> |
ピンクのボーダーの.box
要素の幅はいくつでしょうか?
- 200px
- 240px
- 244px
- 0px
.box
要素はwidth: 100%;
です。親要素の幅は200pxなので、この100%は200pxになります。しかし、その200pxの幅はどこに適用されるのでしょうか?デフォルトでは、コンテンツボックスにそのサイズが適用されます。
ご存じない人のために説明すると、コンテンツボックス(content-box)とはボーダーとパディングの内側の実際にコンテンツを格納する長方形のことです。
コンテンツボックス(content-box)
width: 100%;
の宣言は、.box
のコンテンツボックスを200pxに設定します。padding: 20px;
によりパディングで40px(両側で20px)追加され、ボーダーで4px(各辺2px)追加されます。
これらを計算すると、ピンクのボックスの幅は244pxになります。
そして、244pxのボックスを200px幅の親に詰め込もうとすると、オーバーフローします。
ピンクのボックスは親要素からはみ出してしまう
この挙動は期待通りではありません。
幸いなことに、次のCSSを設定することで、この挙動を変えることができます。
1 2 3 |
*, *::before, *::after { box-sizing: border-box; } |
このルールを適用すると、%はborder-box
に基づいて解決されます。上記の例では、ピンクのボックスが200pxになり、内側のコンテンツボックスは156px(200px-40px-4px)に縮小されます。
私にとってこれは必須のルールです。CSSの操作が大幅に改善されます。
ワイルドカードのセレクタ(*
)を使用して、すべての要素と擬似要素に適用します。ワイルドカードのセレクタは一般的に言われていることとは逆に、パフォーマンスに悪影響を及ぼしません。
参考: * { Box-sizing: Border-box } FTW
ボックスサイズの継承
これと同じことをする方法は、他にもあります。
1 2 3 4 5 6 |
html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } |
この方法は、大規模プロジェクトでborder-box
に移行させる場合に有効です。まったく新しいプロジェクトをゼロから始める場合には必要ありません。シンプルにするために、私はCSSリセットからこれを省きました。
参考までに、移行させる手順も解説します。
まず、ボックスサイズをbox-sizing
プロパティのデフォルト値であるcontent-box
にさせる.legacy
セレクタを作成します。
1 2 3 |
.legacy { box-sizing: content-box; } |
次に、border-box
を使用するように移行されていない要素にこの.legacy
を与えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<body> <header class="legacy"> <nav> <!-- Legacy stuff here --> </nav> </header> <main> <section> <!-- Modern stuff here --> </section> <aside class="legacy"> <!-- Legacy stuff here --> </aside> </main> </body> |
なぜこれが機能すると思いますか?
このCSSがどのように評価されるか解説します。
<header>
は.legacy
が与えられたため、box-sizing: content-box;
が使用されます。
その子の<nav>
はbox-sizing: inherit;
です。親がcontent-box
に設定されているので、<nav>
もcontent-box
に設定されます。
<main>
には.legacy
がないため、親である<body>
から継承します。<body>
は<html>
から継承しています。そして、<html>
にはborder-box
が設定されています。
基本的に、すべての要素はその親からボックスサイズの動作を知ることになります。.legacy
を設定している祖先を持つ場合は、content-box
になります。そうでない場合は、最終的に<html>
を継承し、border-box
になります。
2. デフォルトのマージンを削除
1 2 3 |
* { margin: 0; } |
ブラウザは、マージンを常識的に仮定しています。たとえば、h1
にはパラグラフよりも多くのマージンがデフォルトで設定されています。これらの仮定はワープロの文章では妥当ですが、Webサイトやアプリでは適切ではない可能性があります。
マージンは厄介なもの(CSSにおけるマージンの相殺を徹底解説)で、要素にデフォルトで含まれてなければいいのにと思うことがあります。だから私は、すべてのマージンを取り除くことにしました 🔥
もし、特定のタグにマージンを追加したい場合は、カスタムスタイルで追加することができます。ワイルドカードセレクタ(*
)の詳細度は非常に低いため、このルールを上書きするのは簡単です。
3. 高さは%ベースを使用
1 2 3 |
html, body { height: 100%; } |
CSSで%
ベースの高さを使用したときに、効果がないように見えたことはありませんか?
下記がその例です
main
要素にheight: 100%;
が定義されていますが、ぜんぜん100%
に表示されません!
なぜなら、フローレイアウト(CSSのプライマリなレイアウトモード)では、height
とwidth
は根本的に異なる原理で動作するため、これは機能しません。要素の幅はその親を基準にして計算されますが、要素の高さはその子を基準にして計算されます。
これは複雑なトピックで、この記事の範囲をはるかに超えています。私はこれについてブログで解説するつもりですが、それまでは私のCSS講座「CSS for JavaScript Developers」で学ぶことができます。
このルールを適用すると、main
要素が100%
に表示されることがわかります。
ReactなどのJavaScriptフレームワークを使用している場合は、このルールに3番目のセレクタ(フレームワークで使用されるルートレベルの要素)を追加することもできます。
たとえば、Next.jsのプロジェクトでは、次のようにルールを更新します
1 2 3 |
html, body, #__next { height: 100%; } |
なぜvhを使用しないのか?
なぜ%
ベースの高さにこだわるのか、と思われるかもしれません。そして、なぜ代わりにvh
単位を使用しないのか?
その理由は、vh
単位がスマホで正しく動作しないことがあるからです。スマホのブラウザは、100vh
がスクリーンサイズの100%以上になってしまうからです。
将来的には、CSSの新しい単位がこの問題を解決してくれるでしょう。それまでは%
ベースの高さを使い続けようと思います。
4. アクセシブルなline-heightを追加
1 2 3 |
body { line-height: 1.5; } |
line-height
は、テキストの各行間の垂直方向のスペースを制御します。デフォルト値はブラウザによって異なりますが、1.2
前後になることが多いです。
この単位のない値はフォントのサイズに基づいた比率で、em
単位と同じように機能します。line-height: 1.2;
の場合だと、行間は要素のフォントサイズよりも20%大きくなります。
ここで問題になるのが、1.2
だと行が密集しすぎていて、読みにくくなってしまう人がいることです。WCAGの基準では、line-height
は少なくとも1.5
でなければならないとされています。
参考: Understanding Success Criterion 1.4.12: Text Spacing
しかし、この値では見出しなどの大きな文字を使用した要素では、かなり大きな行になってしまいます。
h1
が1.5
だと行間が大きすぎる
見出しにはこの値を上書きすることをお勧めします。私の理解では、WCAG基準は見出しではなく本文のテキストを対象としています。
calcを使用したline-heightの定義
line-height
を管理するための別の方法を試してみました。
1 2 3 |
* { line-height: calc(1em + 0.5rem); } |
この方法はfont-size
に1.5
などの値を掛けて行の高さを計算するのではなく、font-size
を基準にして各行に一定のスペースを与えます。
本文のテキストには効果を与えず、見出しのような大きな文字に効果を与え、どちらも24pxになります(ブラウザのデフォルトのフォントサイズを想定)。
ただし、欠点が1つあります。body
に定義するのではなく、*
に定義しなければならないことです。これはem
単位の継承がうまくいかないためで、すべての要素に対して1emの値を再計算することはありません。
5. テキストのレンダリングを改善
1 2 3 |
body { -webkit-font-smoothing: antialiased; } |
これにはちょっとした議論の余地があります。
macOSはデフォルトでサブピクセルアンチエイリアスが使用されていました。これは、各ピクセル内のR/G/Bライトを利用して、テキストを読みやすくすることを目的とした技術です。文字のコントラストが向上するため、アクセシビリティの向上につながると考えられていました。Stop “Fixing” Font Smoothingで読んだことがあるかもしれません。
しかし、この記事は2012年のもので、Retinaディスプレイの時代ではありませんでした。現在のピクセルははるかに小さくなり、肉眼では見えません。LEDの物理的な配置も変更されました。最近のモニターを顕微鏡で見ると、R/G/Bのラインが整然と並んだグリッドはもう見ることができません。
2018年にリリースされたmacOS Mojaveで、AppleはOS全体でサブピクセルアンチエイリアスを無効にしました。最新のハードウェアでは、良いことよりも悪いことの方が多いことに気づいたと思います。
紛らわしいことに、ChromeやSafariなどのmacOSブラウザはデフォルトでサブピクセルアンチエイリアスを使用しています。このため、-webkit-font-smoothing
をantialiased
に設定して、明示的にアンチエイリアスをオフにする必要があります。
これがその違いです。
左: アンチエイリアス、右: サブピクセルアンチエイリアス
macOSはサブピクセルアンチエイリアスを使用する唯一のOSで、WindowsやLinuxをはじめ、スマホデバイスには影響しません。
6. メディア要素のデフォルトを改善
1 2 3 4 |
img, picture, video, canvas, svg { display: block; max-width: 100%; } |
奇妙なことに、画像はインライン要素とみなされます。つまり、画像は<em>
や<strong>
のように段落の途中で使用されるべきだということでしょうか。
このことは、私が普段使用している画像の使い方とは一致しません。私は通常、画像を段落やヘッダ、サイドバーと同じように扱い、レイアウト要素としています。
ただし、レイアウトでインライン要素を使用すると、奇妙なことが起こります。マージンでもパディングでもボーダーでもない、謎の4pxのギャップがあったとしたら、それはブラウザがline-height
で追加する「インラインマジックスペース」のせいです。
デフォルトですべての画像にdisplay: block;
を設定することで、さまざまな問題を回避することができます。
ほとんどのブロックレベルの要素は親に合わせて自動的に拡大・縮小しますが、<img>
のようなメディア要素は特別です。置換要素と呼ばれ、同じルールには従いません。
画像の実際のサイズが800×600で、幅500pxの親要素の中に入れたとしても、<img>
要素の幅は800pxになります。
max-width: 100%;
は画像がコンテナより大きくなるのを防ぐもので、私にとってはより理にかなったデフォルトの動作だと思います。
7. フォームのfontに関するデフォルトを削除
1 2 3 |
input, button, textarea, select { font: inherit; } |
もうひとつ奇妙なことがあります。
デフォルトでは、フォームの入力欄やボタンは親からフォントに関するスタイルを継承しません。代わりに、独自の奇妙なスタイルを持っています。
たとえば、<textarea>
はシステムデフォルトの等幅フォントを使用します。テキストの入力欄はシステムデフォルトのサンセリフフォントを使用します。そして、どちらも小さいフォントサイズ(Chromeでは13.333px)が選択されます。
想像のとおり、スマホで13pxのテキストを読むのは非常に困難です。小さなフォントサイズの入力欄をフォーカスすると、ブラウザは自動的にズームして、テキストが読みやすくなります。
残念ながら、これは良いエクスペリエンスではありません
この自動ズームを回避したい場合は、入力欄のフォントサイズを少なくとも1rem / 16pxにする必要があります。この問題に対処するための方法を紹介します。
1 2 3 |
input, button, textarea, select { font-size: 1rem; } |
上記で自動ズームの問題は解決されますが、応急処置に過ぎません。根本的に原因を解決しましょう。フォームの入力欄に独自のフォントスタイルを設定すべきではありません。
1 2 3 |
input, button, textarea, select { font: inherit; } |
font
はあまり使用されないショートハンドですが、font-size
font-weight
font-family
などのフォント関連のプロパティを設定します。inherit
で継承するように設定することで、これらのプロパティが周辺のフォントスタイルと同じになるようにします。
本文のフォントサイズを嫌になるほど小さくしない限り、これですべての問題が一度に解決します 🎉
8. テキストのオーバーフローを回避
1 2 3 |
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; } |
CSSでは、1行にすべての文字を収めるのに十分なスペースがない場合、テキストは自動的に行に折り返しされます。
デフォルトで、アルゴリズムはソフトラップを探します。ソフトラップとは行に分割できる文字のことです。英語でソフトラップの対象となるのはホワイトスペースとハイフンだけですが、言語によって異なります。
行にソフトラップがなく、それが収まらない場合、テキストはオーバーフローします。
ソフトラップがない行は、オーバーフローする
このテキストによるオーバーフローは、いくつかのレイアウトの問題を発生させることがあります。上記では水平スクロールバーが表示されるようになっただけですが、他の状況ではテキストが他の要素と重なったり、画像や動画の下にテキストが隠れたりすることもあります。
overflow-wrap
プロパティを使用すると、行の折り返しのアルゴリズムを調整し、ソフトラップが見つからない場合にハードラップを使用する許可を与えることができます。
overflow-wrap
プロパティを使用すると、改行する
どちらの方法も完璧ではありませんが、少なくともハードラップはレイアウトを崩しません。
同様のルールを提案してくれたSophie Alpertに感謝します。彼女はこのルールをすべての要素に適用することを提案しており、おそらく良いアイデアだと思いますが、個人的にはテストしていません。
また、hyphens
プロパティを追加することもできます。
1 2 3 4 |
p { overflow-wrap: break-word; hyphens: auto; } |
hyphens: auto;
は(ハイフンをサポートする言語で)ハイフンでハードラップを示します。これでさらに、ハードラップがより一般的になります。
これは、非常に狭いテキストの列がある場合には有効ですが、気が散ってしまうかもしれません。私はこのCSSをリセットに含めないことにしましたが、試してみる価値はあります。
9. ルートのスタックコンテキストを作成
1 2 3 |
#root, #__next { isolation: isolate; } |
最後のこれはオプションです。
一般的には、Reactのようなフレームワークを使用している場合にのみ必要です。
isolation
プロパティを使用すると、z-index
を設定しなくても新しいスタックコンテキストを作成できます。
これは、特定の優先度の高い要素(モーダル、ドロップダウン、ツールチップ)がアプリ内の他の要素よりも常に上位に表示されることを保証できるという点で有益です。奇妙なスタックコンテキストのバグもなく、z-index
の軍拡競争もありません。
使用するフレームワークに合わせてセレクタを微調整する必要があります。セレクタは、アプリがレンダリングされるトップレベルの要素を選択します。例えば、create-react-appでは<div id="root">
を使用するので、セレクタは#root
です。
CSSリセット(コピー用)
CSSリセットは自由にコピペして、プロジェクトにお使いください。制限はなく、パブリックドメインとして公開します(記事へのリンクを残していただけるとありがたいです!)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* Josh's Custom CSS Reset https://www.joshwcomeau.com/css/custom-css-reset/ */ *, *::before, *::after { box-sizing: border-box; } * { margin: 0; } html, body { height: 100%; } body { line-height: 1.5; -webkit-font-smoothing: antialiased; } img, picture, video, canvas, svg { display: block; max-width: 100%; } input, button, textarea, select { font: inherit; } p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; } #root, #__next { isolation: isolate; } |
【訳者注】Minifyしたものも掲載しておきます。
1 2 3 4 5 |
/* Josh's Custom CSS Reset https://www.joshwcomeau.com/css/custom-css-reset/ */ *,*::before,*::after{box-sizing:border-box}*{margin:0}html,body{height:100%}body{line-height:1.5;-webkit-font-smoothing:antialiased}img,picture,video,canvas,svg{display:block;max-width:100%}input,button,textarea,select{font:inherit}p,h1,h2,h3,h4,h5,h6{overflow-wrap:break-word}#root,#__next{isolation:isolate} |
このCSSリセットをNPMパッケージとしてリリースしないことを選択したのは、リセットを所有する必要があると感じたためです。このCSSリセットをプロジェクトに導入し、新しいことを学んだり、新しいテクニックを発見したりするたびに、時間をかけて微調整していきます。もちろん必要に応じて、いつでも自分のNPMパッケージを作成してプロジェクトで利用することもできます。あなたはこのコードを所有することで、一緒に成長していくものだということを覚えておいてください。
最後に、A Modern CSS Resetを公開してくれたAndy Bellに感謝します。私は考えを整理することに役立ち、この記事を書くきっかけになりました。
終わりに
私のCSSリセットは非常に短く(宣言はたったの9個!)、それでも記事全体を使ってそれらについて語ることができました。正直なところ、まだまだ書きたいことはたくさんあります。この記事では表面に触れただけです。
CSSは非常に複雑な言語です。ボンネットを開けて、そこで何が起こっているのかを学ばない限り、この言語は常に予測不可能で一貫性のないものに感じられます。メンタルモデルが不完全だと、問題が発生しやすくなります。
ただし、少し時間をかけて言語が実際にどのように機能するかを学べば、すべてがより直感的で予測可能になります。最近はCSSを書くのが楽しくて仕方ありません!
この1年半の間、私はJavaScriptデベロッパーがCSSとの関係を変える手助けをすることに力を入れてきました。CSS for JavaScript Developersというインタラクティブなサイトを公開しました。
CSSが大好物な人、理解できる人になりたいと思っている人のために、このサイトを作成しました。このサイトで全てを学ぶことができます。
sponsors