CSSで、iPhoneのノッチやホームバーに隠れないようにする方法

​​2026年11月14日​CSS​iOS,Playwright,Safari

iPhone のノッチ(画面上部の切り欠き)や画面の角の丸みなどで隠れる部分の幅は、CSS の env() 関数で env(safe-area-inset-top) のように書くと受け取れます。この値を、画面に固定したヘッダーや下のタブの padding に足すと、文字やボタンが隠れる部分を避けて表示されます。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。パソコンで動かす Chromium の画面には切り欠きがないので、Chrome DevTools Protocol の Emulation.setSafeAreaInsetsOverride で safe-area-inset-* の値を上書きして、要素の位置を測りました。上書きに使った 47px や 34px は例として選んだ値で、実際の iPhone の値ではありません。iPhone の実機や Safari では試していません。iPhone で viewport-fit がどう効くかは、MDN と仕様の記述をもとに書いています。

書くのは meta viewport と padding の2か所

まず、HTML の <meta name="viewport"> に viewport-fit=cover を足します。

html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

MDN の meta viewport のページによると、viewport-fit には auto、contain、cover の3つの値があり、cover にするとページが画面いっぱいに広がります。そのぶん、ページの端がノッチなどに重なるので、MDN では safe-area-inset-* の値を使うことを強くすすめています。

次に、画面に固定する要素の padding に、隠れる幅を足します。

css
header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding-top: calc(12px + env(safe-area-inset-top));
  padding-bottom: 12px;
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
}

nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

使える名前は safe-area-inset-top、safe-area-inset-right、safe-area-inset-bottom、safe-area-inset-left の4つです。CSS Environment Variables の仕様では、この4つで「中に置いたものがすべて見える長方形」を、画面の上下左右の端からの距離で表します。長方形の画面では、4つとも0です。この長方形の内側は、セーフエリア(safe area)と呼ばれます。

MDN の env() のページでは、iOS のように画面の下の端に表示が重なる端末では safe-area-inset-bottom に0より大きい値が入り、デスクトップでは0になると説明されています。

縦向きでは、上と下の隠れる部分の分だけずらす

検証環境で、上に47px、下に34px の隠れる部分があるように値を上書きし、固定したヘッダーと下のタブがあるページを表示しました。

縦長の画面の上と下だけを切り出して、「env() なし」と「env() あり」の順に縦に並べた画像。どちらも上に水色のヘッダー「猫の手メモ」、下に「ホーム」「検索」「設定」のタブがあり、画面の上端と下端にピンクの斜線の帯がある。「env() なし」では、ヘッダーの文字とタブの文字がピンクの帯に重なっている。「env() あり」では、ヘッダーの水色の背景はピンクの帯の下まで広がり、文字は帯の下に出ている。タブの文字も帯より上にある
縦向き。画面の上と下を切り出したもの。ピンクの斜線は、値を上書きして作った隠れる部分(上47px、下34px)

env() を使わないページでは、ヘッダーの文字の上端は画面の上から13px で、上の47px の中に入っていました。下のタブの文字は、下端が画面の下から13.8px で、34px の中に入っていました。

env() を足したページでは、ヘッダーの文字の上端は60px(12px+47px+行の余白)まで下がり、タブの文字の下端は画面の下から47.8px まで上がりました。文字はどちらも、隠れる部分の外に出ています。

top や margin ではなく padding で足しているので、ヘッダーとタブの背景は、画面の端まで塗られたままです。動くのは中の文字とボタンだけです。

ヘッダーの下に続く本文も、ヘッダーの高さだけ上をあけているなら、同じ値を足しておきます。左右の余白は、次の節の横向きのためのものです。

css
main {
  padding-top: calc(72px + env(safe-area-inset-top));
  padding-bottom: calc(96px + env(safe-area-inset-bottom));
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
}

横向きでは、左右に max() で最低限の余白を残す

検証環境では、隠れる部分が左右の端に来る横向きの画面を想定して、左右に47px、下に21px の隠れる部分があるように上書きしました。

横長の画面の左上だけを切り出して、2つ縦に並べた画像。上の「env() なし」では、ヘッダーの「猫の手メモ」と本文の書き出しが、左端のピンクの斜線の帯に重なっている。下の「env() あり(左右は max(16px, env(...)))」では、ヘッダーの文字も本文も、ピンクの帯の右側から始まっている
横向き。画面の左上を切り出したもの。ピンクの斜線は、値を上書きして作った隠れる部分(左右47px、下21px のうち左側)

env() を使わないページでは、ヘッダーの文字と本文は左から16px の位置から始まり、47px の帯に重なりました。max(16px, env(safe-area-inset-left)) にしたページでは、どちらも47px の位置から始まりました。

max() を使うのは、ノッチのない画面で余白が0にならないようにするためです。検証環境で値を上書きしないときは、env(safe-area-inset-top) は 0px で、max(16px, env(safe-area-inset-top)) は 16px になりました。上書きして47px にすると、max() の結果も 47px になります。

calc(16px + env(safe-area-inset-left)) でも隠れなくなりますが、ノッチのある側では 16px+47px=63px の余白になります。どちらにするかは、ノッチのすぐ横まで文字を寄せたいかどうかで決めます。

env() の代替値は、ノッチのない画面では使われない

env() には、env(safe-area-inset-bottom, 20px) のように、2つ目の引数に代替値を書けます。ただし、これは「ノッチのない画面で使われる値」ではありません。

仕様では、代替値が使われるのは、指定した名前の環境変数がないときです。検証環境の Chromium 153 で値を上書きしないとき、計算値は次のようになりました。

書いた値 計算値
env(safe-area-inset-bottom, 20px) 0px
env(SAFE-AREA-INSET-BOTTOM, 20px) 20px
env(safe-area-inset-botom, 20px)(つづりの間違い) 20px

Chromium では、ノッチのない画面でも safe-area-inset-bottom は0として存在するので、代替値の20px は使われませんでした。代替値が使われたのは、大文字で書いたときと、つづりを間違えたときです。MDN にも、環境変数の名前は大文字と小文字を区別すると書かれています。

ノッチのない画面で最低限の余白を確保したいときは、代替値ではなく max() を使います。

値が変わると、再読み込みしなくても反映される

検証環境で、ページを開いたまま上書きの値を変えると、padding-left: max(16px, env(safe-area-inset-left)) の計算値は、再読み込みなしで 16px → 47px → 16px と変わりました。

仕様では、safe-area-inset-* は表示の状態によって変わる値です。変わらない最大値がほしいときのために、safe-area-max-inset-top などの4つも決められています。検証環境の Chromium 153 では safe-area-max-inset-bottom も使えましたが、互換性データ(@mdn/browser-compat-data 8.1.8)には、まだこの4つの項目がありません。

パソコンの Chrome で値を上書きして確かめる

Playwright から Chrome DevTools Protocol の Emulation.setSafeAreaInsetsOverride を送ると、env(safe-area-inset-*) の値を好きな数に変えられます。次のコードは、検証で使ったスクリプト(ページの HTML を setContent で読み込んで、位置を測り、スクリーンショットを撮るもの)から、要点だけを抜き出したものです。

js
import { chromium } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext({ viewport: { width: 390, height: 560 } });
const page = await context.newPage();
const cdp = await context.newCDPSession(page);
await cdp.send('Emulation.setSafeAreaInsetsOverride', {
  insets: { top: 47, bottom: 34, left: 0, right: 0 },
});
await page.setContent(html); // html は確かめたいページの HTML の文字列
await page.screenshot({ path: 'safe-area.png' });
await browser.close();

Chrome DevTools Protocol の Emulation ドメインの定義では、このコマンドは試験的(experimental)な扱いです。

この方法では、meta viewport の書き忘れには気づけません。検証環境では、viewport-fit=cover があってもなくても、上書きした値がそのまま env() に入りました。

また、定義には「指定しなかった値は未定義になる」と書かれていますが、検証環境で top だけを上書きすると、env(safe-area-inset-bottom, 20px) は 0px で、代替値は使われませんでした。

対応しているブラウザー

互換性データ(@mdn/browser-compat-data 8.1.8)では、env() は Chrome 69、Edge 79、Firefox 65、Safari 11.1、iOS の Safari 11.3 から使えます。それより前の iOS 11 の Safari では、env() ではなく constant() という名前の関数でした。

​Posted by 86