ビューポート単位「vw, vh, vmin, vmax」を使ったCSSのテクニックのまとめ
Post on:2020年4月2日
sponsorsr
CSSのビューポートを基準にした単位「vw, vh, vmin, vmax」は、ここ数年で多く使用されるようになりました。利点はJavaScriptなしで、レスポンシブ対応のレイアウトや要素のサイズを動的に実装できるからです。vw, vh, vmin, vmaxの知っておくと便利なCSSのテクニックを紹介します。
フォントのサイズ指定、高さいっぱいのコンテンツ、フッタを最下部に配置、デバイスに応じた画像配置、記事は固定幅だけど画像は幅いっぱいなど、実用的なテクニックが満載です。

CSS Viewport Units
by Ahmad Shadeed
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- ビューポートを基準にした単位
 - vw: ビューポートの幅
 - vh: ビューポートの高さ
 - vmin: ビューポートの幅または高さの最小値
 - vmax: ビューポートの幅または高さの最大値
 - ビューポートの単位とパーセント指定の違い
 - ビューポートの単位の使用例
 - レスポンシブ対応の要素
 - コンテナからの脱却
 - 垂直および水平のスペース
 - vminとvmaxの使用例
 - 垂直メディアクエリとビューポートの単位
 - アスペクト比の要素
 - グラフィカルな要素にビューポートの単位を使用する
 - ビューポートの単位で現在の計算値要素をチェック
 - スマホでのスクロールの問題
 - アクセシビリティは重要
 - ビューポートの単位を使う時に便利なツール
 - リソース
 
ビューポートを基準にした単位
仕様によると、ビューポートを基準にした単位(Viewport-percentage Lengths)はWebページのルート要素である最初の包含ブロックのサイズに相対的になります。
参考: CSS Values and Units Module Level 3
包含ブロックの高さまたは幅が変更されると、それに応じてサイズも変更されます。ただし、スクロールバーは存在しないものと見なされます。
ビューポートを基準にした単位には、vw, vh, vmin, vmaxの4種類があります。
まずは、それらの基本的な使い方を見てましょう。
vw: ビューポートの幅
vw単位は、ルート要素の幅(width)に相対的です。vwが1に対して、ビューポートの幅の1%に等しくなります。

vw単位を使用した実装例
CSSは、下記の通りです。
| 
					 1 2 3  | 
						.element {     width: 50vw; }  | 
					
ビューポートの幅が700pxの場合、50vwは次のように計算されます。
| 
					 1  | 
						width = 700*50% = 350px  | 
					
vh: ビューポートの高さ
vh単位は、ルート要素の高さ(height)に相対的です。vhが1に対して、ビューポートの高さの1%に等しくなります。

vh単位を使用した実装例
CSSは、下記の通りです。
| 
					 1 2 3  | 
						.element {     height: 50vh; }  | 
					
ビューポートの高さが290pxの場合、70vhは次のように計算されます。
| 
					 1  | 
						height = 290*70% = 202px  | 
					
ここまでは簡単ですね!
vminとvmaxについても見てましょう。
vmin: ビューポートの幅または高さの最小値
vmin単位は、ビューポートの幅または高さどちらかの最小値に基づいて計算されます。つまり、ビューポートの高さが幅より小さい場合は、高さに基づいて値が計算されます。
次の例を見てみましょう。
ランドスケープモードのスマホがあり、vmin単位を使った要素があります。この場合、幅より高さの値が小さいため、ビューポートの高さに基づいて計算されます。

vmin単位を使用した実装例
CSSは、下記の通りです。
| 
					 1 2 3 4  | 
						.element {     padding-top: 10vmin;     padding-bottom: 10vmin; }  | 
					
vminの計算方法は次のとおりです。
最初に幅(width)と高さ(height)の比較から始まります。

vminは幅と高さを比較し、最小値を採用
vmin単位を使った要素があると、ビューポートの幅と高さを比較し、小さい方の値に基づいて計算されます。
| 
					 1  | 
						padding-top = (10% of height) = 10% * 164 = 32px padding-bottom = (10% of height) = 10% * 164 = 32px  | 
					
vmax: ビューポートの幅または高さの最大値
vmax単位は、vminの反対です。幅または高さの最大値に基づいて計算されます。

vmax単位を使用した実装例
CSSは、下記の通りです。
| 
					 1 2 3 4  | 
						.element {     padding-top: 10vmax;     padding-bottom: 10vmax; }  | 
					
vmaxの計算方法は次のとおりです。
widthとheight間の不等号が逆向きになります。

vmaxは幅と高さを比較し、最大値を採用
vmax単位を使った要素があると、ビューポートの幅と高さを比較し、大きい方の値に基づいて計算されます。
| 
					 1  | 
						padding-top = (10% of width) = 10% * 350 = 35px padding-bottom = (10% of width) = 10% * 350 = px  | 
					
ビューポートの単位とパーセント指定の違い
ビューポートの単位は、ページのルート要素に基づきますが、パーセント指定は親コンテナに基づきます。そのため、この2つは異なり、それぞれに用途があります。
ビューポートの単位の使用例
実際にビューポートの単位を使う例と、それらの実装方法について解説します。
フォントのサイズにビューポートの単位を使う
ビューポートの単位は、レスポンシブ用のタイポグラフィに最適です。例えば、記事のタイトルを例に見てましょう。

フォントのサイズにビューポートの単位を使う
font-sizeに「5vw」を定義します。
| 
					 1 2 3  | 
						.title {     font-size: 5vw; }  | 
					
タイトルのfont-sizeは、ビューポートの幅(vw)に基づいて増減されます。上記のCSSは、ビューポートの幅の5%がフォントのサイズに指定されています。
このまま使用したくなるかもしれませんが、下記のGIFアニメを見てみてください。
font-sizeに「5vw」を定義
スクリーンがスマホのサイズでは、フォントが小さすぎます。これはアクセシビリティとユーザエクスペリエンスにとって悪いことです。スマホにおけるフォントの最小サイズは、14pxです。上記では、10px未満になってしまいました。
この問題を解決するには、フォントが一定サイズから下回ることがないよう最小サイズを指定します。calc()関数の出番です!
| 
					 1 2 3  | 
						.title {     font-size: calc(14px + 2vw); }  | 
					
calc()関数でベースを14pxにし、2vwを加算します。これで、font-sizeの値が14pxより小さくなることはありません。
もう一つ考慮すべき重要なことは、27インチiMacのような大きなスクリーンでフォントのサイズがどのようになるかです。あなたが推測する通り、フォントは95pxと巨大になります。
これを解決するには、メディアクエリを使用します。
| 
					 1 2 3 4 5  | 
						@media (min-width: 1800px) {     .title {         font-size: 40px;     } }  | 
					
font-sizeを丁寧に定義することで、サイズを適切にできます。複数のメディアクエリを定義する必要があるかもしれませんが、それはプロジェクトのコンテキストに依存します。
スクリーンいっぱいのコンテンツにビューポートの単位を使う
ビューポートの高さ100%を占めるコンテンツが必要な場合があります。これは「フルスクリーン セクション」と呼ばれるもので、ビューポートの高さの単位(vh)を使うと簡単です。

フルスクリーン セクションにビューポートの単位を使う
フルスクリーン セクションのheightに「100vh」を定義します。
| 
					 1 2 3 4 5 6 7  | 
						.section {     height: 100vh;     display: flex;     flex-direction: column;     justify-content: center;     align-items: center; }  | 
					
height: 100vh;を定義することで、セクションの高さはビューポートの100%になります。また、コンテンツを水平・垂直方向の中央に配置するためにFlexboxを使用しています。
フッタにビューポートの単位を使う
コンテンツが少ないページを大きなスクリーンで表示した時、フッタが最下部に配置されていないことがあります。もちろん、これは正常なことですが、改善の余地があります。

フッタの下に空白ができてしまう
コンテンツ全体にビューポートの高さと等しい高さを与えることができれば、フッタを最下部に配置することができます。動的に実装するのは難しいですが、ビューポートの単位を使用すると、簡単に実装できます。
1. ビューポートの単位をcalc()で使用する
ヘッダとフッタの高さが固定されている場合は、calc()関数とビューポートの単位を組み合わせて実現できます。
| 
					 1 2 3 4 5 6 7 8  | 
						header, footer {     height: 70px; } main {     height: calc(100vh - 70px - 70px); }  | 
					
このテクニックは、常に機能するとは限らないことに注意してください。なぜなら、ヘッダとフッタの高さは固定値である必要があります。
2. ビューポートの単位とFlexboxを使用する(おすすめ)
body要素の高さとして100vhを加えることにより、Flexboxでmain要素に残りのスペースを確保させることができます。
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						body {     min-height: 100vh;     display: flex     flex-direction: column; { main {     /* This will make the main element take the remaining space dynamically */     flex-grow: 1; }  | 
					
これでコンテンツの長さに関係なく、フッタは最下部に配置されます。

フッタは常に最下部に配置される
レスポンシブ対応の要素
レスポンシブ対応のデザインがあり、3つのデバイス(スマホ・タブレット・デスクトップ)に対応してみましょう。各デバイスには画像要素があり、CSSでこれを100%のレスポンシブで実装するとします。

レスポンシブ対応の要素
CSS Gridとビューポートの単位を使用することで、これを完璧に実装できます。
| 
					 1 2 3 4 5  | 
						<div class="wrapper">   <div class="device laptop"></div>   <div class="device mobile"></div>   <div class="device tablet"></div> </div>  | 
					
ビューポートの単位をgrid-*プロパティで使用していることに注目してください。さらに、ビューポートの単位はborder, border-radiusなど他のプロパティでも使用されています。これらはすべて、流動的なデザインになります。
| 
					 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  | 
						.wrapper {   display: grid;   grid-template-columns: repeat(20, 5vw);    grid-auto-rows: 6vw; } .mobile {    position: relative;   z-index: 1;   grid-column: 2 / span 3;   grid-row: 3 / span 5; } .tablet {   position: relative;   z-index: 1;   grid-column: 13 / span 7;    grid-row: 4 / span 4;   border-bottom: 1vw solid #a9B9dd;   border-right: solid 3vw #a9B9dd; } .laptop {   position: relative;   grid-column: 3/span 13;   grid-row: 2 / span 8; } /* ビューポートの単位は、bottom, left, right, height, border-radiusに使用されます */ .laptop:after {     content:"";     position:absolute;     bottom: -3vw;     left: -5.5vw;     right: -5.5vw;     height: 3vw;     background-color: #a9B9dd;     border-radius: 0 0 1vw 1vw; }  | 
					
これで下記のように実装されます。
コンテナからの脱却
記事のレイアウトに最も適した使用例があることに気がつきました。親要素の幅が制限されている場合でも、子要素の幅をビューポートの100%にすることができます。
下記のような感じです。

親の幅が制限されていても、子の画像は幅いっぱいに表示
これを実現するには、ビューポートの単位と配置プロパティを使用します。
| 
					 1 2 3 4 5 6 7 8  | 
						.break-out {     width: 100vw;     position: relative;     left: 50%;     right: 50%;     margin-left: -50vw;     margin-right: -50vw; }  | 
					
このCSSがどのように機能するかを解説します。
1. width: 100vw;
これは最も重要なポイントで、画像のwidthをビューポートの100%に等しくします。

子の画像にwidth: 100vw;を与える
2. margin-left: -50vw
画像を中央に配置するために、まずはビューポートの幅の半分で負のマージンを与えます。

中央に配置するために、まずは負のマージンを与える
3. left: 50%
最後に、親の幅の50%の値で画像を右に押し出します。

これで画像が中央に配置される
垂直および水平のスペース
もう一つ興味深い使用例は、要素間のスペースにビューポートの単位を使用することです。margin, top, bottom, grid-gapなどのプロパティで使用できます。それらで使用することで、スペースはビューポートの幅や高さに基づいて設定されるため、レイアウトをより動的にするのに便利です。
モーダル
モーダルの場合、ビューポートの上部から押し出す必要があります。ほとんどの場合は、topプロパティに%やpxの値を使用することが多いです。しかし、ビューポートの単位を使用すると、スペースをビューポートの高さに応じて変更できます。

上部のスペースは動的になる
モーダルに、top: 20vh;を加えます。
| 
					 1 2 3  | 
						.modal-body {     top: 20vh; }  | 
					
上部のスペースがどのようになるかは、GIFアニメをご覧ください。
動的に変化する上部のスペース
ページのヘッダ
ページのヘッダとは、ページの紹介として機能するセクションです。多くの場合、タイトルと説明文があり、その高さは固定または上下にpaddingが定義されています。ここでは、paddingに注目してみます。
まずは、通常使用されるCSSを見てみてください。
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						.page-header {     padding-top: 1rem;     padding-bottom: 1rem; } @media (min-width: 800px) {     .page-header {         padding-top: 4rem;         padding-bottom: 4rem;     } }  | 
					
スマホだと垂直方向のpaddingが小さくなり、ビューポートが大きくなるとpaddingも大きくなります。
ビューポートの単位にするとどうなるでしょうか?
| 
					 1 2 3 4 5 6 7 8  | 
						.page-header {     padding-top: 10vh;     padding-bottom: 10vh; } .page-header h2 {     margin-bottom: 1.5vh; }  | 
					
ヘッダのpaddingとタイトルの下のmarginに、vh単位を使用しています。スペースがそれぞれどのように変化するか注目してみてください。
paddingとmarginにビューポートの単位を使用
アイテムのグリッド
動的なスペースの使用例をもう一つ紹介します。アイテムのグリッドで、記事のグリッドやサービス紹介などで使用できます。記事のグリッドを例に見てましょう。

アイテム間のスペースが動的になる
grid-gapプロパティでビューポートの単位を使用することで、目的の結果を得ることができます。calc()関数を使用していることにも注目してください。calc()関数を使用する目的は、ベースとなる垂直と水平方向のギャップを持たせることです。
| 
					 1 2 3 4 5  | 
						.wrapper {     display: grid;     grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));     grid-gap: calc(14px + 1vh) calc(14px + 0.5vw); }  | 
					
vminとvmaxの使用例
vminとvmaxの使用例を調べてみたら、CSS-Tricksで見つけました。
参考: Simple Little Use Case for vmin
ページのヘッダ要素の上下のpaddingに関するものです。ビューポートが小さい場合(スマホ)で、paddingが少なくなってしまうことがあります。vminを使用することで、ビューポートのより小さいサイズ(幅または高さ)に基づいてpaddingを適切に設定できます。
| 
					 1 2 3  | 
						.page-header {     padding: 10vmin 1rem; }  | 
					
paddingの上下に10vminを定義することで、解決します。
スマホでも、ヘッダ要素の上下のpaddingを適切にする
垂直メディアクエリとビューポートの単位
以前、垂直メディアクエリに関する記事を私は書きました。今回の記事に関連しているので、取り上げたいと思います。
参考: Use Cases For CSS Vertical Media Queries
ランドスケープモードでの全高セクション
垂直メディアクエリを使用することで、ユーザーのスマホやタブレットが横向きのランドスケープモードになっているかを確認することができます。横向きになっていないのであれば、height: 100vh;を使用してセクションをビューポートの全高にする意味がなくなります。

ランドスケープモードで、セクションを高さいっぱいに
この問題を解決するには、CSSを下記のように記述します。
| 
					 1 2 3 4 5  | 
						@media (min-height: 400px) {     .section {         height: 100vh;     } }  | 
					
もしくは、orientationのメディアクエリを使用できます。
| 
					 1 2 3 4 5  | 
						@media (orientation: landscape) {     .section {         height: 100vh;     } }  | 
					
アスペクト比の要素
vw単位を使用して、ビューポートのサイズに関係なくアスペクト比を維持するレスポンシブ要素を実装することができます。
最初にアスペクト比を決める必要があります。ここでは、9/16にします。
| 
					 1 2 3 4  | 
						section {     /* 9/16 * 100 */     height: 56.25vw; }  | 
					
アスペクト比を維持している要素
グラフィカルな要素にビューポートの単位を使用する
私のネーミングが正しいかどうかは分かりませんが、下記の例で私の言いたいことが伝わることを願っています。
トップボーダー
ページ上部にトップボーダーがあるサイトを見かけたことはありませんか? ほとんどはそのカラーに、ブランドカラーを使用しています。

ページ上部のトップボーダー
borderのデフォルト値が3pxであることをサポートしてみましょう。その固定値をどうやってビューポートのものに変換するするか?
それを行うvwの計算式は、下記の通りです。
| 
					 1  | 
						vw = (Pixel Value / Viewport width) * 100  | 
					
ビューポートの幅は、ピクセル値と目的にvw単位の比率を計算するために使用されます。
ここでは、トップボーダーを追加します。
| 
					 1 2 3  | 
						.header {     border-top: 4px solid #8f7ebc;   }  | 
					
私の場合、ビューポートの幅は1440px(これは決まった数字ではないので、自分が使用する数字に置き換えてください)です。
| 
					 1  | 
						vw = (4 / 1440) * 100 = 0.277  | 
					
| 
					 1 2 3  | 
						.header {     border-top: 0.277vw solid #8f7ebc;   }  | 
					
さらに良い点は、ベースとなるピクセル値を利用して、ビューポートの単位を追加できます。
| 
					 1 2 3  | 
						.header {     border-top: calc(2px + 0.138vw) solid $color-main; }  | 
					
セクションのナンバリング
ナンバーやアイコンのあるセクションアイテムの場合、ビューポートの単位の利点を活用できます。

セクションのナンバリング
数字を含む丸があり、テキストコンテンツがあります。どちらもビューポートの単位で流動的にサイズ調整する必要があります。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14  | 
						/** 1. flex、width、heightプロパティに流動的な値を使用 2. line-heightは、テキストを垂直方向に中央揃えするために使用 3. フォントのサイズ 4. 丸とテキストの間隔 **/ .point:before {     flex: 0 0 calc(24px + 4vw); /* [1] */     width: calc(24px + 4vw); /* [1] */     height: calc(24px + 4vw); /* [1] */     line-height: calc(24px + 4vw); /* [2] */     font-size: calc(14px + 1vw); /* [3] */     margin-right: calc(10px + 0.5vw); /* [4] */ }  | 
					

実装の仕組み
ビューポートの単位で現在の計算値要素をチェック
ビューポートの単位で現在の計算値要素をチェックするのに役立つツールを作成してみました。下記の例を見てみてください。

よくあるページのレイアウト
各要素のclass名とプロパティを追加し、ビューポートのサイズを変更すると、値が動的に更新されます。これはデベロッパーツールで要素ごとに計算された値を見るよりはるかに便利です。
ビューポートのサイズを変更すると、値が動的に更新される
スマホでのスクロールの問題
スマホでは100vhにした場合でもスクロールしてしまうという問題があります。原因は、アドレスバーの高さがビューに含まれているからです。Louis Hoebregtsは、それを解決するシンプルな方法について記事を書いています。
参考: The trick to viewport units on mobile
翻訳: スマホで要素を高さいっぱいに表示したいのに、期待通りに表示されない時の解決方法
| 
					 1 2 3 4  | 
						.my-element {   height: 100vh; /* Fallback for browsers that do not support Custom Properties */   height: calc(var(--vh, 1vh) * 100); }  | 
					
| 
					 1 2 3 4  | 
						// First we get the viewport height and we multiple it by 1% to get a value for a vh unit let vh = window.innerHeight * 0.01; // Then we set the value in the --vh custom property to the root of the document document.documentElement.style.setProperty('--vh', `${vh}px`);  | 
					
これでスクリーンに、100%分のコンテンツが表示されます。

左: ビフォー、右: アフター
アクセシビリティは重要
レイアウト全体を構築するなど、ビューポートの単位を広範囲で使用する場合、アクセシビリティが重要となります。レイアウトを拡大・縮小すると、ビューポートの単位で実装した要素は期待通りに拡大縮小されません。これは、ズーム機能を使用しているユーザーにとっては問題になります。
SāraSoueidānのツイートにあるように、ビューポートの単位だけでフォントのサイズを決めるのはアクセシビリティ的に悪いことです。
下記のように固定値と組み合わせた方がよいでしょう。
| 
					 1 2 3  | 
						.title {     font-size: calc(16px + .3vw); }  | 
					
ビューポートの単位を使用することが実装に適していることを確認し、しっかり検証してください。
ビューポートの単位を使う時に便利なツール
pxをvWに変換するツールがあります。
ビューポートの単位を使うプロジェクトで役立つかもしれません。

リソース
- Full Width Containers in Limited Width Parents
 - Fun with Viewport Units
 - Simple Little Use Case for vmin
 - MinMaxing: Understanding vMin and vMax in CSS
 
これで終わりです。
コメントや提案があれば、@shadeed9までお願いします。
sponsors











