margin, paddingなど、レスポンシブに対応したスペースをCSSで効率的に定義する方法
Post on:2018年11月27日
Webページやスマホアプリのレイアウトに使用する、margin, paddingなどのスペースをCSSで効率的に定義する方法を紹介します。
em, remの相対的な単位を使い、calc()を効果的に活用し、拡張性やメンテナンス性にも優れたデザインシステムを構築できます。
Create your design system, part 4: Spacing
by CodyHouse
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- はじめに
- CSSの変数を使用して、スペースのシステムを設定する方法
- すべてのコンポーネントにデフォルトのpaddingを設定する方法
- marginのユーティリティ
- スペース値を追加する場合
- 固定のスペース値が必要な場合
- スペースを定義したSCSSファイル
はじめに
元々は、近日公開予定の「Web Components」のために作成したものですが、グローバルのCSSとして利用できるので、一足先に公開します。
CSSの変数を使用して、スペースのシステムを設定する方法
スペースのシステムを設定する最初のステップは、スペースのスケールを定義することです。スケールを定義するには、「1.単位または基本値」と「2.乗数」が必要になります。
まずは基本となるスケールを数値で作成します。
1 2 3 4 5 6 7 8 9 |
:root { --space-xxs: 4px; --space-xs: 8px; --space-sm: 12px; --space-md: 20px; --space-lg: 32px; --space-xl: 52px; --space-xxl: 84px; } |
この値はフィボナッチ数列に基づいており、各値はその前の2つの値を加算して求められています(4+8=12px)。
参考: Spacing
これは良くも悪くも、あくまでも基本です。なぜなら、1つのスペース値を変更したい場合は、再び計算を行い、他のすべての値を手動で更新する必要があります。
この数値をベースにして、デザインシステムで利用できるようにします。
値を定義する変数を導入し、calc()関数を使用してベース(--space-unit)の値を元に計算式で各値を取得することができます。
1 2 3 4 5 6 7 8 9 10 |
:root { --space-unit: 16px; --space-xxs: calc(0.25 * var(--space-unit)); // 4px --space-xs: calc(0.5 * var(--space-unit)); // 8px --space-sm: calc(0.75 * var(--space-unit)); // 12px --space-md: calc(1.25 * var(--space-unit)); // 20px --space-lg: calc(2 * var(--space-unit)); // 32px --space-xl: calc(3.25 * var(--space-unit)); // 52px --space-xxl: calc(5.25 * var(--space-unit)); // 84px } |
実装に使えるレベルにだいぶ近づいてきました。上記のコードは、--space-unitの値を乗算して各値を定義しています。フィボナッチ数列が依然として乗数に適用されることに注目してください。変更したい場合は、--space-unitの値を更新するだけです。
次に、絶対単位を取り除いて、相対単位に置き換えます。
ここでは--space-unitの「16px」を「1em」に更新します。
1 2 3 4 5 6 7 8 9 10 |
:root { --space-unit: 1em; --space-xxs: calc(0.25 * var(--space-unit)); --space-xs: calc(0.5 * var(--space-unit)); --space-sm: calc(0.75 * var(--space-unit)); --space-md: calc(1.25 * var(--space-unit)); --space-lg: calc(2 * var(--space-unit)); --space-xl: calc(3.25 * var(--space-unit)); --space-xxl: calc(5.25 * var(--space-unit)); } |
この変更はごく小さな調整のように感じるかもしれませんが、実際には大きな変化です。em単位は、フォントのサイズ設定に使用される便利な相対単位です。ほとんどのブラウザでは、CSSでスタイルを適用する前のデフォルトのフォントサイズは16pxです。そのため、わたし達は「1em=16px」を想定することができます。
ただし、要素のフォントサイズを編集した場合、1emは16pxでなくなりますが、定義したfont-sizeと同じになります。これはコントロールの欠如のように思われるかもしれませんが、レスポンシブへの強力な近道となります。
この方法は例えば、--text-base-size変数(1emに等しい)に比率を掛けて、すべてのフォントサイズから得られるタイポグラフィのシステムを作成することもできます。つまり、--text-base-size変数はタイプシステム全体のコントローラーであることを意味します。もし、あなたがメディアクエリで--text-base-size変数の値を変更するなら、すべてのテキストのサイズはそれに応じて変化します。
1 2 3 4 5 |
@include breakpoint(md) { :root { --text-base-size: 1.25em; } } |
変数を1つ変更するだけで、すべてのテキストのサイズが変更されます。
それだけではありません!
スペースの単位も1emで定義されているため、--text-base-sizeを更新した際にはスペースにもその更新が適用されます。
このテクニックがタイポグラフィとスペースにどのように影響するか見てみましょう。
例えば、単一の変数(--text-base-size)を更新するとします。追加のメディアクエリは必要ありません。この場合に必要なのは、スペースの変数を使用してコンポーネントレベルでpaddingとmarginを設定するだけです。
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 |
.header__top { background: var(--black); padding: var(--space-sm); text-align: center; a { color: var(--white); @include fontSmooth; } } .header__main { border-bottom: 1px solid var(--color-border); padding-top: var(--space-sm); padding-bottom: var(--space-sm); background: var(--white); } .header__nav { ul { display: flex; } li { margin-right: var(--space-md); &:last-child { margin-right: 0; } } } |
--text-base-sizeの値を変更せずに、すべてのスペース値を更新したい場合はどうすればよいでしょうか?
--space-unitの値を更新するだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
:root { --space-unit: 1em; --space-xxs: calc(0.25 * var(--space-unit)); --space-xs: calc(0.5 * var(--space-unit)); --space-sm: calc(0.75 * var(--space-unit)); --space-md: calc(1.25 * var(--space-unit)); --space-lg: calc(2 * var(--space-unit)); --space-xl: calc(3.25 * var(--space-unit)); --space-xxl: calc(5.25 * var(--space-unit)); } @include breakpoint(md) { :root { --space-unit: 1.25em; } } |
このテクニックでは1px単位の調整はできませんが、シンプルでしかもメンテナンスが簡単になります。さらに、レスポンスの新しいレベルにも対応できます。すべてのコンポーネントとデザイン要素をそれぞれ調整するのではなく、2つ3つの変数を編集するだけで対応することができるようになります。
すべてのコンポーネントにデフォルトのpaddingを設定する方法
異なるコンポーネントで同じpaddingが必要になる時がありませんか? コンポーネントのpaddingを変数で設定することは、CSSファイルのどこかに定義したpadding値を探すのに多くの時間を費やした後に学んだテクニックです。
例えば、下記のようなコンポーネントがあるとします。
同じpaddingが必要な異なるコンポーネント
先ほどの_spacing.scssファイルに、コンポーネント用のpaddingを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
:root { --space-unit: 1em; --space-xxs: calc(0.25 * var(--space-unit)); --space-xs: calc(0.5 * var(--space-unit)); --space-sm: calc(0.75 * var(--space-unit)); --space-md: calc(1.25 * var(--space-unit)); --space-lg: calc(2 * var(--space-unit)); --space-xl: calc(3.25 * var(--space-unit)); --space-xxl: calc(5.25 * var(--space-unit)); /* components padding */ --component-padding: var(--space-sm); } .component { padding-top: var(--component-padding); } |
padding用に新しく値を用意する必要はありません。--component-padding変数にスペースの変数を定義するだけです。こうすることで、他の同様の場合にどのスペース変数が使用されているかを確認する必要はなくなります。
marginのユーティリティ
marginを含め、コンポーネントのCSSにpaddingを直接含めることは問題ありませんが、レイアウトで問題を起こす可能性があります。コンポーネントをレイアウト内の定義とすると、marginとpositionの定義はコンポーネントのCSSではなく、レイアウトのCSSに格納する必要があります。
例えば、同じコンポーネントを異なるレイアウトで使用する場合に、あるケースではmargin-bottomを適用する必要があり、別のケースでは適用しないということがあります。こういった場合も想定すると、margin-bottomはコンポーネントの定義ではなく、レイアウトの定義とした方がよいかもしれません。
そのため、marginにユーティリティのclassを用意しておくと便利です。
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 |
.margin-top, .margin-top--md { margin-top: var(--space-md); } .margin-top--sm { margin-top: var(--space-sm); } .margin-top--lg { margin-top: var(--space-lg); } .margin-top--xl { margin-top: var(--space-xl); } .margin-top--xxl { margin-top: var(--space-xxl); } .margin-bottom, .margin-bottom--md { margin-bottom: var(--space-md); } .margin-bottom--sm { margin-bottom: var(--space-sm); } .margin-bottom--lg { margin-bottom: var(--space-lg); } .margin-bottom--xl { margin-bottom: var(--space-xl); } .margin-bottom--xxl { margin-bottom: var(--space-xxl); } |
オプションとして特定のブレークポイントでmargin値を更新することができ、ビューポートが大きくなると自動的に値が増えます。
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 |
@include breakpoint(md) { .margin-top, .margin-top--md { margin-top: var(--space-lg); } .margin-top--sm { margin-top: var(--space-md); } .margin-top--lg { margin-top: var(--space-xl); } .margin-top--xl { margin-top: var(--space-xxl); } .margin-top--xxl { margin-top: var(--space-xxxl); } .margin-bottom, .margin-bottom--md { margin-bottom: var(--space-lg); } .margin-bottom--sm { margin-bottom: var(--space-md); } .margin-bottom--lg { margin-bottom: var(--space-xl); } .margin-bottom--xl { margin-bottom: var(--space-xxl); } .margin-bottom--xxl { margin-bottom: var(--space-xxxl); } } |
スペース値を追加する場合
要素にmarginを設定している場合、space-mdが大きすぎるのに対して、space-smが小さすぎるように見える場合はどうなりますか?
あらかじめ定義された変数を使って作業することは、システムの保守性のために妥協する必要があることを意味します。
しかし、定義されていない値を使う必要がある場合、システムを更新することができます。
1 2 3 |
.nav__item { margin-right: calc(var(--space-sm) * 1.1); } |
上記のように新しいスペース値を追加できます、しかしこの新しい値も同じシステムでコントロールできるようにしてください。
固定のスペース値が必要な場合
このスペースのシステムでは、すべてのmargin, padding(およびタイポグラフィ要素)が2つの変数を使用して同時に更新できます。しかしビューポートのサイズ変更に関わらず、スペースの値を変更したくない場合があるかもしれません。
そのためには別の単位、つまりremでスペースを定義する必要があります。remはemとは異なり、ルート(html)のfont-sizeに相対的です。そのため、本文のfont-sizeを編集しても影響を受けません(本文は--text-base-sizeで定義します)。
私はemをremに置き換えることを提案するのではなく、emとremを組み合わせることを提案します。
固定のスペース値を定義する最も簡単な方法は、下記のようになります。
1 2 3 |
.nav__item { margin-right: 0.5rem; } |
固定の値を定義するのも前述のスペース値の追加と同様に、システムにはない値を導入することを意味します。
例えば、新しい人があなたのチームに参加し、CSSファイルを開き、要素に0.5remのmarginを設定したことに気付きます。理想的なフローでは、新しい人にスペースのシステムを伝えているか、プロジェクトにCSSのガイドラインがあるか、スペースがどのように定義され使用されているのか詳細に伝えることができます。しかし、現実のフローでは、新しい人はrem値でスペースを設定すると理解してしまう危険性があります。
混乱を避けるために、変数を使った方が理想的です。変数を使うと、_spacing.scssファイルを確認するフローになり、安全です。
レスポンシブ対応のスペース値の定義に加え、固定値にも対応する定義は、下記のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
:root { /* spacing values */ --space-unit: 1em; --space-xxs: calc(0.25 * var(--space-unit)); --space-xs: calc(0.5 * var(--space-unit)); --space-sm: calc(0.75 * var(--space-unit)); --space-md: calc(1.25 * var(--space-unit)); --space-lg: calc(2 * var(--space-unit)); --space-xl: calc(3.25 * var(--space-unit)); --space-xxl: calc(5.25 * var(--space-unit)); /* fixed values - not affected by --space-unit or --text-base-size */ --space-unit-fixed: 1rem; --space-xxs-fixed: calc(0.25 * var(--space-unit-fixed)); --space-xs-fixed: calc(0.5 * var(--space-unit-fixed)); --space-sm-fixed: calc(0.75 * var(--space-unit-fixed)); --space-md-fixed: calc(1.25 * var(--space-unit-fixed)); --space-lg-fixed: calc(2 * var(--space-unit-fixed)); --space-xl-fixed: calc(3.25 * var(--space-unit-fixed)); --space-xxl-fixed: calc(5.25 * var(--space-unit-fixed)); } |
スペースを定義したSCSSファイル
上記をすべてまとめたファイルは、下記のようになります。
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
/* -------------------------------- Spacing -------------------------------- */ :root { /* spacing values */ --space-unit: 1em; --space-xxxs: calc(0.25 * var(--space-unit)); --space-xxs: calc(0.375 * var(--space-unit)); --space-xs: calc(0.5 * var(--space-unit)); --space-sm: calc(0.75 * var(--space-unit)); --space-md: calc(1.25 * var(--space-unit)); --space-lg: calc(2 * var(--space-unit)); --space-xl: calc(3.25 * var(--space-unit)); --space-xxl: calc(5.25 * var(--space-unit)); --space-xxxl: calc(8.5 * var(--space-unit)); /* components padding */ --component-padding: var(--space-sm); } // optional - edit space unit and padding of all components at a specific breakpoint @include breakpoint(md) { :root { --space-unit: 1.25em; --component-padding: var(--space-md); } } /* vertical margins */ .margin-top, .margin-top--md { margin-top: var(--space-md); } .margin-top--sm { margin-top: var(--space-sm); } .margin-top--lg { margin-top: var(--space-lg); } .margin-top--xl { margin-top: var(--space-xl); } .margin-top--xxl { margin-top: var(--space-xxl); } .margin-bottom, .margin-bottom--md { margin-bottom: var(--space-md); } .margin-bottom--sm { margin-bottom: var(--space-sm); } .margin-bottom--lg { margin-bottom: var(--space-lg); } .margin-bottom--xl { margin-bottom: var(--space-xl); } .margin-bottom--xxl { margin-bottom: var(--space-xxl); } @include breakpoint(md) { .margin-top, .margin-top--md { margin-top: var(--space-lg); } .margin-top--sm { margin-top: var(--space-md); } .margin-top--lg { margin-top: var(--space-xl); } .margin-top--xl { margin-top: var(--space-xxl); } .margin-top--xxl { margin-top: var(--space-xxxl); } .margin-bottom, .margin-bottom--md { margin-bottom: var(--space-lg); } .margin-bottom--sm { margin-bottom: var(--space-md); } .margin-bottom--lg { margin-bottom: var(--space-xl); } .margin-bottom--xl { margin-bottom: var(--space-xxl); } .margin-bottom--xxl { margin-bottom: var(--space-xxxl); } } |
このスペースのシステムにフィードバックや提案がある場合は、お知らせください。
この記事があなたにとって役立つことを願っています。
sponsors