CSSの単位(em, rem)を使った、これから取り入れていきたい実装テクニックのまとめ
Post on:2016年9月23日
CSSの相対的な単位(em, rem)を使って、Webページで使われるUIコンポーネントのさまざまなサイズを変化させるスタイルシートのテクニックを紹介します。
em, remは文字の大きさだけでなく、画像やアイコンのサイズ、背景、フォーム、ボタン、配置など、さまざまな指定に利用できます。
Building Resizeable Components with Relative CSS Units
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- フォントのサイズを変化
- 縦と横の比率を維持したボタン
- 縦と横の比率を維持した画像
- box-shadowを使ったボーダーの幅と高さ
- アイコンの幅と高さ、そして間隔も
- キャプション付き画像
- 装飾された背景
- アイコン付き検索フォーム
- チェックボックスで実装されたトグル
- ブロックの一行の長さ
- ボタンとしてのSVGアイコン
- リストのコンテナ
- モーダルウインドウのアイコン
- 疑似要素を使ったアイコン
- グラデーション
- CSSスプライト
- emとremのコンビネーション
- 相対的な単位を使ってサイトを制作
- ブラウザのズーム機能
- 重要: あまり知られていないemの注意点
フォントのサイズを変化
最初はプロポーショナルフォントのサイズを変化させるシンプルなテクニックです。
コンポーネントは、3つの要素で構成されています。
- サブタイトル
- タイトル
- 左のボーダー
このようなコンポーネントのサイズを変化させる時は、フォントのサイズだけでなく、ボーダーもそれに合わせて変化させる必要があります。
「border-left: 0.25em solid #4a90e2;」のように、ボーダーもサイズ指定の単位に「em」を使用することで簡単に実現できます。
1 2 3 4 5 6 |
<article class="post"> <a href="#"> <span class="post-category">Featured</span> <h2 class="post-title">Building Dynamic Components is Awesome</h2> </a> </article> |
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 |
.posts-wrapper { font-size: 30px; } .post { margin: 16px 0; a { display: block; background: #f2f2f2; padding: 15px; border-left: 4px solid $color-brand; text-decoration: none; transition: background 0.3s ease; &:hover { background: $color-brand; color: #fff; span, h2 { color: #fff; } } } span { display: block; font-size: 14px; color: #7d7d7d; transition: color 0.2s ease; } h2 { font-size: 22px; color: $color-brand; margin: 0; transition: color 0.2s ease; } } .post--enhanced { margin-top: em(16); margin-bottom: em(16); a { padding: em(15); border-left-width: em(4); } span { font-size: em(14); } h2 { font-size: em(22); } } |
縦と横の比率を維持したボタン
ボタンはバリエーションが必要なUIエレメントです。より大きなボタンはユーザーのアクションを呼びかける重要な存在です。
これはpaddingにemを使うことで実現でき、font-sizeとpaddingの両方を通じて簡単にボタンの大きさを変化させることができます。角丸ボタンでborder-radiusを使う時は、同様にemで指定します。
1 2 3 4 5 |
<button class="button">Save Settings</button> <button class="button button--medium">Save Settings</button> <button class="button button--large">Save Settings</button> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
.button { display: block; font-size: 1em; background: $color-brand; padding: em(10) em(16); color: #fff; border-radius: em(4); margin-bottom: 16px; font-weight: 300; border: 0; } .button--2x { font-size: 150%; } .button--3x { font-size: 200%; } |
縦と横の比率を維持した画像
画像も下記のように、著者と日付が変化するように写真画像も変化する必要があります。ここで注目すべき点は、ブルーのボーダーです。フォントのサイズが変化するにつれ、その高さも変化します。
1 2 3 4 5 6 7 |
<div class="bio"> <img src="author.jpg" alt="Photo of author Ahmad Shadeed"> <div class="bio__meta"> <h3><b>By:</b> Ahmad Shadeed</h3> <time>Posted on August 5, 2016</time> </div> </div> |
1 2 3 4 5 6 7 8 9 10 |
.bio h3 { font-size: 1em; } .bio time { font-size: 0.875em; } .bio img { width: 3.125em; height: 3.125em; } |
box-shadowを使ったボーダーの幅と高さ
emの単位指定をしておくといいもう1つのプロパティが、box-shadowです。box-shadowで実装されたボーダーはemを使うことで、拡大縮小が可能になります。
1 2 |
<h2 class="headline"><span>About Author</span></h2> <h2 class="headline headline--dynamic"><span>About Author</span></h2> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.headline { span { display: inline-block; box-shadow: inset 0 em(-4) 0 0 #e7e7e7; } } .headline--dynamic { font-size: 240%; } .headline--fixed { font-size: 240%; span { box-shadow: inset 0 -4px 0 0 #e7e7e7; } } |
アイコンの幅と高さ、そして間隔も
引用文のように、テキストとアイコンをセットにしたコンポーネントは、フォントサイズが変化した時に、アイコンの幅と高さ、そしてテキストとの間隔も変化する必要があります。ここでも相対的な単位で指定するのが役に立ちます。
px指定を使ってしまうと、アイコンとテキストが重なってしまったり、非常に離れてしまったりします。
1 2 3 4 5 6 7 8 |
<blockquote class="quote"> <p> <span> Building dynamic web components using modular design concepts is awesome. <em>- Ahmad Shadeed</em> </span> </p> </blockquote> |
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 |
.quote { position: relative; margin: 0; background: $color-brand; padding: 1.5em 2em; padding-left: 4.5em; border-radius: em(5); margin-bottom: 16px; line-height: 1.5; p { margin: 0; font-size: 2em; } em { all: initial; color: #fff; font-family: inherit; } span { color: #fff; box-shadow: inset 0 em(-4) 0 0 transparentize(#fff, 0.6); } &:before { content: ""; position: absolute; top: em(34); left: em(30); background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/182774/quotes.svg') no-repeat; background-size: 100%; height: em(30); width: em(30); } } .quote--px { padding: 24px 32px; padding-left: em(72); border-radius: 5px; p { font-size: 32px; } span { color: #fff; box-shadow: inset 0 -8px 0 0 transparentize(#fff, 0.6); } &:before { content: ""; top: 30px; left: 30px; width: 30px; height: 30px; } } .quote--dynamic { font-size: 1.35em; } |
キャプション付き画像
キャプションが重なって配置された画像がどのようにアレンジされるか想像してみてください。もちろんこのコンポーネントもfont-sizeに基づいて、左と上のオフセット、パディング、そしてシャドウも変化させることができます。
1 2 3 4 |
<figure class="figure"> <img src="sunrise.jpg" alt="Sunrise"> <figcaption>The feeling you got from watching the sunrise is amazing.</figcaption> </figure> |
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 |
.figure { position: relative; width: 600px; margin: 0 auto; margin-bottom: 1em; img { display: block; width: 100%; height: 550px; object-fit: cover; } figcaption { position: absolute; top: em(20); left: em(-30); right: 0; background: #3f51b5; color: #fff; padding: 1em; box-shadow: em(-5) em(5) 0 0 transparentize(#000, 0.85); font-size: 1.75em; } } .figure--px { figcaption { top: 20px; left: -30px; padding: 1em; box-shadow: -5px 5px 0 0 transparentize(#000, 0.85); font-size: 1.75em; } } |
装飾された背景
このコンテンツのブロックは、タイトルの背景にサークルと破線を配置しています。このサークルもfont-sizeが変化する時に、変化します。単にサイズを変化させるだけでなく、border-radiusは破線の太さと同様に相対的にします。
1 2 3 4 5 6 |
<section class="block"> <h3 class="block__title">Content outline</h3> <div class="block__content"> <p>Description to be there....</p> </div> </section> |
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 |
.block { border-radius: em(5); //overflow: hidden; margin-bottom: 1em; } .block__title { position: relative; font-size: em(24); background: $color-brand; padding: em(8); color: #fff; margin: 0; //overflow: hidden; display: flex; flex-direction: row-reverse; align-items: center; &:after { content: ""; position: absolute; left: em(4); top: 0; width: em(40); height: em(40); border-radius: 50%; background: #000; opacity: 0.5; transform: scale(1.75); } &:before { content: ""; flex: 1; margin-left: em(8); border-bottom: em(1) dashed transparentize(#fff, 0.5); } } .block__content { font-size: em(18); padding: em(8); color: #000; background: lightgrey; p { font-size: 100%; color: inherit; margin: 0; } } .block--px { .block__title { font-size: 24px; padding: 8px; &:after { content: ""; left: 4px; top: 4px; width: 40px; height: 40px; } &:before { content: ""; margin-left: 8px; } } .block__content { font-size: 18px; padding: 8px; } } .clip-circle { .block, .block__title { overflow: hidden; } .block__title { &:after { opacity: 0.25; } } } |
アイコン付き検索フォーム
ボタンとしてアイコンを使うことは普通ですが、検索フォームのようなinput要素にアイコンを使うこともできます。背景画像として配置したアイコンのサイズ、そしてサイズ変更に伴って位置が変わるのを阻止するためにパディングも変化させます。
1 2 3 4 |
<form class="search"> <label for="search">Enter keyword:</label> <input type="search" id="search" placeholder="What are you searching about?"> </form> |
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 |
.search { margin-bottom: 1.35em; label { display: block; margin-bottom: 6px; } input { width: em(400); font-size: em(16); padding: em(10); padding-left: em(40); border-radius: em(5); border: em(2) solid #b4b4b4; background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/182774/search.svg') left em(10) center / em(24) em(24) no-repeat; &:focus { border-color: #000; outline: 0; } } } .search--2x { input { font-size: 1.25em; } } .search--px { input { font-size: 1.25em; padding: em(10); padding-left: em(40); border-radius: em(5); border: 2px solid #b4b4b4; background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/182774/search.svg') left 8px center / 24px 24px no-repeat; } } |
チェックボックスで実装されたトグル
最近ではチェックボックスをトグルスイッチのような見た目で実装することもあります。トグルにしたチェックボックスもfont-sizeの変化に合わせることができます。
1 2 3 4 5 |
<form action="" class="switch"> <p>Do you want to subscribe?</p> <input type="checkbox" name="" id="switch" class="off-screen"> <label for="switch"><span class="off-screen">Do you want to subscribe?</span></label> </form> |
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 |
.off-screen { position: absolute; left: -9999px; width: 0; height: 0; } .switch { //font-size: 24px; margin-bottom: 1em; p { margin: 0; margin-bottom: 0.35em; } label { position: relative; display: block; width: 90px; height: 40px; border: 2px solid #b4b4b4; border-radius: 40px; cursor: pointer; &:before { content: ""; position: absolute; right: 4px; top: 2px; width: 32px; height: 32px; border-radius: 50%; background: #b4b4b4; transition: transform 0.25s ease, background 0.25s ease; } } input:focus + label, input:hover + label { border-color: $color-brand; box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5); } input:checked + label { &:before { transform: translateX(calc(-100% - 16px)); background: $color-brand; } } } .switch--2x { label { width: em(90); height: em(40); border: em(2) solid #b4b4b4; border-radius: em(40); &:before { content: ""; right: em(4); top: em(3.5); width: em(32); height: em(32); } } input:checked + label { &:before { transform: translateX(calc(-100% - 1em)); } } } |
ブロックの一行の長さ
ブロックに見出し・パラブラフ・リンクがあるコンポーネントで、それぞれ水平のスペースを持っています。max-widthで最大幅を指定するのは素晴らしい方法ですが、pxで指定するのではなく、相対的な単位で指定すると更にフレキシブルなコンポーネントを実装できます。
1 2 3 4 5 |
<div class="hero"> <h2>This is title for this hero section</h2> <p>And this paragraph is a sub title, as you know I'm writing an article about using em units to build dynamic components.</p> <a href="#">Read about hero</a> </div> |
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 |
.hero { background: #ececec; padding: 1.5em; > * { outline: 1px solid grey; background: lightgrey; } h2, p { margin: 0; padding: 0; } h2 { margin-bottom: 0.25em; font-size: 1.75em; } p { margin-bottom: 1em; max-width: em(450); font-size: 1.25em; } a { display: inline-block; background: $color-brand; color: #fff; padding: 0.7em 1.5em; text-decoration: none; } } |
ボタンとしてのSVGアイコン
アイコンフォントを使用する一番の理由は、フォントのサイズが変化した時に、アイコンも自動的にサイズが変化することです。しかしこれはアイコンフォントに限ったものではなく、img要素でも実装でき、さらにインラインのsvg要素でも実装することができます。
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 |
<ul class="social"> <li class="social__item"> <a href="#"> <svg width="32" height="32" viewBox="0 0 32 32"> <!-- SVG Data --> </svg> Like on Facebook </a> </li> <li class="social__item"> <a href="#"> <svg width="32" height="32" viewBox="0 0 32 32"> <!-- SVG Data --> </svg> Follow on Twitter </a> </li> <li class="social__item"> <a href="#"> <svg width="32" height="32" viewBox="0 0 32 32"> <!-- SVG Data --> </svg> Follow on Dribbble </a> </li> </ul> |
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 |
.social { padding: 1em; list-style: none; background: #f2f2f2; font-size: 1em; &--2x { font-size: 1.2em; } &--3x { font-size: 1.4em; } &--px { font-size: 1.5em; .social__item { svg { width: 35px; height: 35px; } } } } .social__item { display: inline-block; margin-right: 1em; svg { display: inline-block; vertical-align: middle; width: em(35); height: em(35); fill: #000; } a { display: block; text-decoration: none; color: #000; &:hover { opacity: 0.6; } } } |
リストのコンテナ
リストのビュレットをデザインすることはよくあることです。こういったリストもfont-sizeが変化した時に、同じように変化することができます。ビュレットだけそのままのサイズだったり、大きくしたら重なってしまったとかはNGです。
1 2 3 4 5 6 |
<ul class="list"> <li>Go to example.com and click on Register</li> <li>Enter your email address</li> <li>Pick a strong password</li> <li>Congrats! You now have an account</li> </ul> |
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 |
.list { list-style: none; padding: 0; counter-reset: list; margin-bottom: 40px; font-size: 1em; li { position: relative; display: flex; align-items: center; padding-left: em(50); counter-increment: section; margin-bottom: em(16); min-height: em(40); &:before { content: counter(section); position: absolute; left: 0; font-size: em(16); color: #000; width: em(40); height: em(40); background: #ececec; text-align: center; line-height: em(40); border-radius: 50%; } } } |
モーダルウインドウのアイコン
モーダルウインドウ、アラートパネル、リストなどを開いた時に、閉じるアイコンが必要です。このアイコンも相対的にサイズを変化させることができます。
1 2 3 4 5 6 7 8 9 10 |
<div class="item"> <div class="modal"> <header class="modal__header"> <h2>Add new item</h2> <a href="#"> <!-- SVG Data --> </a> </header> </div> </div> |
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 |
.modal { width: 650px; } .modal__header { background: lightgrey; border-radius: em(5); display: flex; justify-content: space-between; align-items: center; padding: 1em; position: relative; h2 { font-size: 1em; margin: 0; } svg { width: 1em; height: 1em; } } .modal__header { border-radius: 0.3125em; padding: 1em; } .modal__header h2 { font-size: 1em; } .modal__header svg { width: 1em; height: 1em; } |
疑似要素を使ったアイコン
ハンバーガーメニューのように、空要素に疑似要素を使ってアイコンを実装することがあります。これももちろん拡大・縮小ができるようにすべきです。
1 2 3 4 5 6 |
<div class="item"> <button class="menu"> Open Menu <span></span> </button> </div> |
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 |
.menu { position: relative; display: block; appearance: none; width: em(40); height: em(30); margin-bottom: 20px; background: transparent; border: 0; text-indent: -999px; cursor: pointer; transition: opacity 0.2s ease; &:hover { opacity: 0.75; } span { position: absolute; left: 0; top: 0; width: 100%; height: em(6); background: $color-brand; border-radius: em(5); &:after, &:before { content: ""; position: absolute; left: 0; right: 0; height: 100%; background: inherit; border-radius: inherit; } &:after { top: em(12); } &:before { top: em(24); } } } |
グラデーション
これまで、背景のサイズのために相対的な単位をどのように使うことができるかを見てきました。ここで少し新しいテクニックを見てみましょう。グラデーションも相対的な単位で指定することができます。
1 2 3 4 5 |
<div class="item"> <div class="box box-1"></div> <div class="box box-2"></div> <div class="box box-3"></div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.box-1 { background: linear-gradient( to right, #4a90e2 0, #4a90e2 0.625em, #1b5dab 0.625em, #1b5dab 1.875em, #4a90e2 0, #4a90e2 3.125em ); background-size: 1.25em 100%; } |
CSSスプライト
ラスター画像のように、px単位で扱う固定サイズのものがあります。しかしそれが相対的な単位を使うことができないことを意味しません。例えば、CSSスプライトの背景におけるポジションやサイズもemで指定することができます。
1 2 3 4 5 6 7 8 |
<div class="item"> <ul class="sprites"> <li class="sprites__item facebook"></li> <li class="sprites__item twitter"></li> <li class="sprites__item linkedin"></li> <li class="sprites__item google"></li> </ul> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
.sprites__item { display: inline-block; width: em(40); height: em(40); background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/182774/icons.png) no-repeat; background-size: auto 95%; &.twitter { background-position: em(-43) 0; } &.linkedin { background-position: em(-90) 0; } &.google { background-position: em(-137) 0; } } |
emとremのコンビネーション
ここまで単位にemを使用してきました。emは親要素のfont-sizeに基づいて、サイズを確定させる単位です。似た単位にremがあります、これはルートに対して相対的にサイズを確定させます。
このremとemのコンビネーションで、さらにダイナミックに適用することができます。
例えば下記のコンポーネントのように、見出しと本文のサイズはremでルートに対して、画像のサイズやスペースはemで親要素に対してという実装もできます。
1 2 3 4 5 6 7 |
<div class="item"> <div class="member"> <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/182774/4nHiO7jc.jpg" alt=""> <h2>Ahmad Shadeed</h2> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Esse obcaecati nisi quam ad quisquam pariatur placeat! Quis modi assumenda ratione animi perspiciatis, nesciunt, optio commodi quae, repellendus hic atque ea!</p> </div> </div> |
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 |
.member { position: relative; background: #ececec; padding: 2rem; min-height: em(110); padding-right: em(130); font-size: 1.2em; margin-bottom: 2em; img { position: absolute; right: em(20); top: em(20); width: em(100); height: em(100); background: #fff; padding: 0.25em; object-fit: cover; } p, h2 { margin: 0; } h2 { font-size: 1.2rem; } p { font-size: 1rem; line-height: 1.45; } } |
相対的な単位を使ってサイトを制作
ここまで実装したコンポーネントは単独でも使用できますが、組み合わせることでページ全体が相対的なサイズに対応します。拡大・縮小すべきサイズは変化し、サイズ変更の影響を受けない箇所はそのままです。
ブラウザのズーム機能
emベースのデザインは、ブラウザのズーム機能にも対応しており、互換性があります。pxベースのデザインだとズーム機能は問題になることがしばしばあります。
重要: あまり知られていないemの注意点
単位にemを使用する時、注意しなければならないことは、基づくfont-sizeは最も近い親要素であることです。
1 2 3 4 5 6 7 |
.parent { font-size: 20px; .child { /* 20pxに基づいて、20px x 1.5 = 30px */ font-size: 1.5em; } } |
「.child」のサイズは、最も近い親要素「.parent」のサイズに基づいて算出されます。
しかしemの中の他のサイズを決める時は、現在の要素の新たに調整されたサイズに基づきます。
1 2 3 4 5 6 7 8 9 10 11 12 |
.parent { font-size: 20px; .child { /* 20pxに基づいて、20px x 1.5 = 30px */ font-size: 1.5em; /* 1.5emがベースになり(20pxではない)、30px x 1 =30px */ border: 1em solid black; } } |
最終的には同じ値(30px)ですが、emでの指定値が異なるので、見た目で混乱してしまうことがあります。複数のemを指定する時は充分に注意を払い、指定してください。
sponsors