iOS 5 の Safari では、Web 制作にかかわる変更がいくつも入りました。中でも影響が大きかったのが、position: fixed が画面に固定されるようになったことと、overflow: scroll を指定した要素を 1 本の指でスクロールできるようになったことです。どちらも、画面の上や下にメニューを固定した、アプリのような画面を作るのに欠かせない機能でした。ここでは当時の事情と、今の書き方をまとめます。
iOS 5 より前に起きていたこと
それまでの iPhone の Safari では、position: fixed を指定しても、要素が画面に固定されませんでした。Safari は、ページを拡大・縮小しながらその一部を画面に映す仕組みになっていて、fixed の要素は画面ではなく、ページ全体を基準に配置されていたからです。スクロールすると、固定したはずのメニューがページと一緒に流れていきました。
また、overflow: scroll の要素の中身は、2 本の指でなでないとスクロールできず、慣性 (指を離したあとも少し滑り続ける動き) もありませんでした。そのため、JavaScript でタッチの動きを読み取って、スクロールを再現するライブラリがよく使われていました。
iOS 5 では、position: fixed の要素が画面に固定されるようになり、overflow: scroll の要素も 1 本の指でスクロールできるようになりました。さらに -webkit-overflow-scrolling: touch を指定すると、ページ全体と同じ、慣性のあるスクロールになりました。
/* iOS 5 のころの書き方 (今は -webkit-overflow-scrolling は不要) */
.scroll-panel {
height: 300px;
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
今の iOS の Safari では、指定しなくても要素の中が慣性スクロールするので、-webkit-overflow-scrolling は書かなくてかまいません。スクロールする要素の端まで来たときに、ページ全体までスクロールが伝わってしまうのを止めたいときは、overscroll-behavior: contain を使います。
上に固定するなら position: sticky
見出しやメニューを画面の上に固定したいなら、今は position: sticky がまず候補になります。sticky の要素は、ふだんは通常の位置に置かれ、スクロールして指定した位置 (top: 0 など) まで来たところで止まります。
.site-header {
position: sticky;
top: 0;
z-index: 10;
background: #fff;
}
fixed と違って通常の流れから外れないので、下の内容が隠れないように余白を足す必要がありません。ただし、祖先の要素に overflow: hidden などが付いていると、その要素を基準に止まろうとするため、ページのスクロールでは止まらなくなります (理由は overflow の基本と float を囲む効果 でも触れています)。また、sticky の要素は、親要素の範囲の中でしか止まりません。
画面の高さは svh・dvh で指定する
スマートフォンのブラウザーでは、アドレスバーなどがスクロールに合わせて出たり隠れたりするので、表示領域の高さが変わります。多くのブラウザーで 100vh はバーが隠れたときの大きいほうの高さになるため、height: 100vh の要素は、バーが出ているときに下が切れて見えます。
そこで、表示領域の状態に合わせた単位が加わりました。
svh: バーが出ているときの、小さいほうの高さlvh: バーが隠れたときの、大きいほうの高さdvh: その時点の高さ。バーの出し入れに合わせて変わる
.hero {
min-height: 100vh; /* 新しい単位に対応していないブラウザー向け */
min-height: 100svh; /* バーが出ていても、はみ出さない高さ */
}
.app-shell {
height: 100dvh; /* そのときの表示領域いっぱいにする */
}
dvh はバーの出し入れのたびに値が変わり、レイアウトが動くことがあります。最初の画面をちょうど埋めたいだけなら、svh のほうが落ち着いた表示になります。
画面の端は env(safe-area-inset-*) で避ける
角が丸い画面や、上部に切り欠きのある画面、画面の下にホームバー (ホーム画面に戻る操作のための帯) がある端末では、画面の端の一部が隠れたり、操作と重なったりします。viewport の meta 要素に viewport-fit=cover を指定すると、ページを画面の端まで広げたうえで、避けるべき幅を env(safe-area-inset-*) で CSS から読み取れます。
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
/* ホームバーと重ならないように、下の余白を足す */
padding: 8px 16px calc(8px + env(safe-area-inset-bottom, 0px));
}
env() の 2 つ目の値は、その値が無い環境で使われる代わりの値です。safe-area-inset-top・safe-area-inset-right・safe-area-inset-left もあり、端末を横に向けたときに、左右の切り欠きを避けるのにも使います。
まとめ
- iOS 5 で、
position: fixedと、要素の中の 1 本指のスクロール (-webkit-overflow-scrolling: touchで慣性つき) が使えるようになった - 今は
-webkit-overflow-scrollingは不要。上に固定する見出しにはposition: stickyが使いやすい - 画面の高さには、
100vhの代わりにsvhやdvhを使う viewport-fit=coverとenv(safe-area-inset-*)で、切り欠きやホームバーを避ける