【CSS】iPhoneのノッチやセーフエリアに対応するenv()関数の使い方

当ページのリンクには広告が含まれています。
  • URLをコピーしました!

モバイルで下のボタンがホームインジケーターに重なって押しにくいことはありませんか。
端末が変わるとドロワーやトーストの位置が少しずれて見えることもあります。
その数ピクセルが使いやすさに直結します。

この記事では env(safe-area-inset-bottom) を使って、ノッチやジェスチャーバーと重ならない配置にする方法を説明します。
前提設定から実装手順、max()clamp() を使った調整までを順に見ていきます。
対応していない環境でも崩れにくい書き方も紹介します。

読み終えるころには「見える」「押せる」「ずれない」レイアウトを短時間で作れるようになります。
今日の案件から使える、わかりやすいコードを用意します。

目次

セーフエリアとenv()について

ノッチは画面上部の切り欠き部分です。
フロントカメラやセンサーを収めるためのスペースで、画面の一部が欠けて見えます。
端末ごとにサイズや形が違います。

ホームインジケーターは画面下部の細いバーです。
iPhoneや一部Androidで、スワイプ操作の起点になります。
このバーに近い領域は指で隠れやすく、ボタンが押しづらくなります。

セーフエリアはノッチやホームインジケーターに重ならない安全な表示範囲です。
端末が「ここに配置すると見やすい」と教えてくれる領域です。
CSSの env() 関数を使うと、この値をそのまま余白にできます。

/* 例:フッターに下端の安全余白を足します */
.page-footer {
  padding-bottom: env(safe-area-inset-bottom);
}

端末が env(safe-area-inset-bottom) の値を提供します。
これにより、ホームインジケーター上にCTAが重なりにくくなります。
未対応の端末でも崩れないよう、フォールバックを入れておくと安心です。

.page-footer {
  padding-bottom: env(safe-area-inset-bottom, 16px);
}

次に、似た名前の関数と違いを整理します。
var() は開発者が定義したCSS変数を参照します。
env() はOSやブラウザが提供する環境変数を参照します。
用途が異なるため、目的に合わせて使い分けます。

:root { --space-m: 16px; }

.section {
  padding-bottom: var(--space-m);
}

実装のコツは、「まず下端を守る」ことです。
ホームインジケーター付近はもっとも操作が集中します。
最初に下端の余白だけ入れて、体験を早く安定させます。

/* 固定CTAを下端に安全配置 */
.c-ctaFixed {
  position: fixed;
  left: 0;
  right: 0;
  bottom: env(safe-area-inset-bottom, 0);
  padding: 12px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0));
}

この設定で、見た目の高さは保ったまま、端末ごとの安全余白を自動で確保できます。

viewport設定と表示モードの前提

セーフエリアを正しく使うには、まずビューポートを整える必要があります。
ここが合っていないと env(safe-area-inset-bottom) が常に 0 になることがあります。

最低限の meta viewport

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

ズーム倍率と幅をそろえます。
これがないと相対計算が不安定になります。

端まで描画するための viewport-fit=cover

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

画面の端までコンテンツを広げます。
cover にしたら、自分で安全余白を付ける前提になります。

全体に安全余白を一括で付与

/* レイアウトの親要素に適用 */
.l-app {
  padding:
    env(safe-area-inset-top, 0)
    env(safe-area-inset-right, 0)
    env(safe-area-inset-bottom, 0)
    env(safe-area-inset-left, 0);
}

非対応端末では 0 として扱われます。

固定UIにだけ安全余白を足す

/* 下固定CTA */
.c-ctaFixed {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 12px 16px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0));
}

/* 下寄せトースト */
.c-toast {
  position: fixed;
  left: 50%;
  bottom: calc(16px + env(safe-area-inset-bottom, 0));
  transform: translateX(-50%);
}

見た目の余白 + セーフエリアの加算が基本です。
bottom を直接 env() にするより、padding で持ち上げるほうが安定します。

非対応や差分に備えるフォールバック

/* まず固定値で安全に */
.c-ctaFixed { padding-bottom: 16px; }

/* 対応環境だけ強化 */
@supports (padding: max(0px)) {
  .c-ctaFixed {
    padding-bottom: max(16px, calc(12px + env(safe-area-inset-bottom, 0)));
  }
}

/* 値が大きすぎる端末は上限を設定 */
.c-ctaFixed {
  padding-bottom: clamp(16px, calc(12px + env(safe-area-inset-bottom, 0)), 40px);
}

通常のブラウザはツールバーの出入りで見え方が変わります。縦横の回転とスクロール後の位置を実機で確認します。

PWAやインストール済みWebアプリはセーフエリア値が変わりやすいです。固定ボトムUIは見た目の余白に env(safe-area-inset-bottom) を足す前提で設計します。

アプリ内WebViewは実装差が大きいです。OS分岐より @supports で機能があるときだけ強化します。

要点は三つです。最初に viewport-fit=cover を決めます。固定UIは padding で持ち上げます。固定値で始めて、@supports を通った環境だけ env() を足します。

該当するパーツの作り方

ドロワーメニュー

まずはドロワーです。右からスライドし、内側の上下に env(safe-area-inset-*) を足して、ノッチとホームインジケーターを避けます。動作イメージを以下で確認できます。

See the Pen iPhoneのノッチやセーフエリアに対応 by Suzuki Kazuma (@build_suzuki) on CodePen.

次にフッターとトーストです。どちらも位置は下端に固定し、見た目の余白 + env(safe-area-inset-bottom)padding で加算します。bottom を直接 env にせず、padding で持ち上げると崩れにくいです。

追従CTA

<div class="c-cta-fixed" role="region" aria-label="固定CTA">
  <a class="c-cta-fixed__btn" href="/contact">お問い合わせ</a>
</div>
.c-cta-fixed {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0; /* 位置は0で固定 */
  padding: 12px 16px;
  /* 見た目の余白 + セーフエリア。上限で暴れを抑えます */
  padding-bottom: clamp(16px, calc(12px + env(safe-area-inset-bottom, 0)), 40px);
  background: #111;
  color: #fff;
  box-shadow: 0 -2px 10px rgba(0,0,0,.08);
}
.c-cta-fixed__btn {
  display: block;
  width: min(600px, 92vw);
  margin: 0 auto;
  text-align: center;
  line-height: 48px;
  font-weight: 700;
}
  • まずは固定値で安全にし、対応環境では env() を採用します。
  • 値が過大になる端末は clamp() で上限を決めます。

トースト

<div class="c-toast" role="status" aria-live="polite">
  保存しました。
</div>
.c-toast {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  /* 下端距離 + セーフエリア */
  bottom: calc(16px + env(safe-area-inset-bottom, 0));
  padding: 10px 14px;
  background: #222;
  color: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 24px rgba(0,0,0,.15);
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease, transform .2s ease;
}
.c-toast.is-active {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}
  • アニメーションの終点も同じ式(calc(16px + env(...)))にそろえるとズレにくいです。
  • フォーム送信後などは短時間だけ .is-active を付け外しします。

フォールバックと上限管理でUIを安定化

env() に依存しすぎない設計にします。
まず固定値で形を作り、対応環境だけ @supports で強化し、max()clamp() で最小値と上限を管理します。
bottom は 0 に保ち、padding に一度だけ env() を足す流れが安全です。

固定値から始めて対応環境だけ強化

.c-cta-fixed { padding-bottom: 16px; }

@supports (padding: max(0px)) {
  .c-cta-fixed {
    padding-bottom: max(16px, calc(12px + env(safe-area-inset-bottom, 0)));
  }
}

上限を決めて過大な余白を防ぐ

.c-cta-fixed {
  padding-bottom: clamp(16px, calc(12px + env(safe-area-inset-bottom, 0)), 40px);
}

二重加算を避ける計算順序にする

/* 悪い例 */
.c-bad {
  bottom: env(safe-area-inset-bottom, 0);
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0));
}

/* 良い例 */
.c-good {
  bottom: 0;
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0));
}

機能検出で段階的に強化

.c-toast { bottom: 16px; }

@supports (bottom: calc(16px + env(safe-area-inset-bottom, 0))) {
  .c-toast { bottom: calc(16px + env(safe-area-inset-bottom, 0)); }
}

トークンと合成して保守性を高める

:root { --space-m: 12px; }

.c-drawer__panel {
  padding-bottom: calc(var(--space-m) * 2 + env(safe-area-inset-bottom, 0));
}

固定値で土台を作り、対応環境では env() を重ねて最小値を担保しつつ、clamp() で上限を縛ります。
位置は bottom: 0 のままにして、見た目の余白へ一回だけ env() を加えることで、端末差やWebViewの挙動差でも崩れにくくなります。

再利用できるようにする

毎回計算式を書く手間を減らすために、カスタムプロパティを用意します。
既存コンポーネントにクラスを足すだけで、簡単に実装できます。

:root {
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --safe-bottom: env(safe-area-inset-bottom, 0);
  --safe-top: env(safe-area-inset-top, 0);
}

/* 下端の安全余白を付与(上限つき) */
.u-safe-bottom {
  padding-bottom: clamp(var(--space-3), calc(var(--space-3) + var(--safe-bottom)), 40px);
}

/* 上下の安全余白を付与(ヘッダーやドロワーに) */
.u-safe-block {
  padding-top: calc(var(--space-3) + var(--safe-top));
  padding-bottom: clamp(var(--space-3), calc(var(--space-3) + var(--safe-bottom)), 40px);
}

/* 下寄せコンポーネントの位置調整(トーストなど) */
.u-bottom-offset {
  bottom: calc(var(--space-4) + var(--safe-bottom));
}

あとは既存の要素にクラスを足すだけです。

<!-- 固定CTAを安全に -->
<div class="c-cta-fixed u-safe-bottom">
  <a class="c-cta-fixed__btn" href="/contact">お問い合わせ</a>
</div>

<!-- ドロワーパネルの内側に上下の安全余白 -->
<aside class="c-drawer__panel u-safe-block">…</aside>

<!-- トーストの終点位置を安全に -->
<div class="c-toast u-bottom-offset">保存しました。</div>

ポイントは二つです。まず 位置は bottom: 0 を基本 とし、見た目の余白に安全分を足します。
次に 上限は clamp で管理 し、値が大きい端末でも間延びしないようにします。

これでサイト全体へ一括適用しやすくなります。

実機での確認ポイント

まずは現在の余白が正しく効いているかを、端末を跨いで同じ手順で確認します。
iPhoneとAndroidの実機で、縦横の回転、アドレスバーの表示・非表示、スクロール後の戻りを試し、固定フッターやドロワー、トーストがホームインジケーターやノッチに重ならないかを見ます。
フォームにフォーカスしてキーボードを出し、閉じた直後に位置がずれないかも確かめます。

確認を素早くするために、デバッグ用の見た目を一時的に足します。セーフエリア分だけ色を変えて「どれだけ持ち上がっているか」を一目で把握します。

/* デバッグ用。安全余白のぶんだけ背景色を重ねて見えるようにします */
.u-debug-safe {
  background:
    linear-gradient(#ffe8e8, #ffe8e8) top / 100% env(safe-area-inset-top, 0) no-repeat,
    linear-gradient(#e8f6ff, #e8f6ff) bottom / 100% env(safe-area-inset-bottom, 0) no-repeat;
}

値そのものを確認したいときは、CSSカスタムプロパティに env を受けて、JavaScriptで読み取ります。
計測した値が 0 のままなら、viewport-fit=cover や適用要素を見直します。

:root {
  --safe-top: env(safe-area-inset-top, 0);
  --safe-bottom: env(safe-area-inset-bottom, 0);
}
// コンソールで現在のセーフエリアを確認します
const cs = getComputedStyle(document.documentElement);
console.log('safe-top:', cs.getPropertyValue('--safe-top').trim());
console.log('safe-bottom:', cs.getPropertyValue('--safe-bottom').trim());

固定UIの最終確認では、bottom: 0 を維持したまま padding-bottom に env を一度だけ加算しているかを見ます。
二重に入っていると過大な余白になります。
アニメーションを使っている要素は、終点の bottomtransform がセーフエリア込みの位置にそろっているかをチェックします。
最後に、リンクをタップしてページ遷移した後、戻る操作でも位置が再計算されて崩れないかまで確認します。

まとめ

セーフエリア対応は、ほんの数ピクセルの違いで操作のしやすさが変わります。
まずは viewport-fit=cover を設定し、位置は bottom: 0 のままにします。
余白は padding に env(safe-area-inset-bottom) を加えて持ち上げるのが安定します。

固定フッターやトーストは、余白を「見た目+セーフエリア」で計算する形にすると、どの端末でも自然に見えます。
ドロワーの場合は、上下の paddingenv() を加えることで、ノッチやホームインジケーターを避けながら表示できます。

未対応の環境には固定値を使い、対応している環境だけ @supports で強化します。
さらに max()clamp() を組み合わせて、最小値と上限を決めておくと安心です。

Web制作に使えるおすすめの書籍

私が実際に購入して役に立った書籍を紹介します。
気になる本があればぜひ読んでみてください!

この記事が気に入ったら
いいね または フォローしてね!

  • URLをコピーしました!
  • URLをコピーしました!
目次