CSSで、要素を画面の真ん中に置く方法

​​CSS​HTML,Playwright

要素を画面の縦と横の真ん中に置くには、親の要素を display: grid にして place-items: center を付け、min-height: 100dvh で親を画面の高さいっぱいに広げます。スクロールしても同じ場所に出したいダイアログなどは、position: fixed で画面に固定してから真ん中に置きます。画面全体をおおう層の中でフレックスボックスを使って真ん中に置くと、中身が画面より高くなったときに上の端が切れることがあるので、その直し方も紹介します。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS だけのページを Playwright で開き、画面の大きさを変えながら、getBoundingClientRect() で要素の位置を調べました。Firefox と Safari、スマートフォンの実機は試していません。ヘッドレスの Chromium では、スクロールバーが画面の幅を取りませんでした。スクロールバーが幅を取る環境での位置は調べていません。

白い枠の中に「真ん中の箱」と書かれた水色の箱がある。マウスで枠の右下の角をつかんで、枠を横長、縦長、低い横長と3回広げたり縮めたりする。どの大きさでも箱は枠の真ん中にあり、枠の下に表示した、箱の上と下の余白、左と右の余白の数字は、それぞれ同じ値になる。角を離した瞬間だけ、数字がピンクになる
画面の大きさを3回変えても、箱が真ん中に残る様子。白い枠が画面の代わりで、枠の大きさはスクリプトで変えている(マウスの絵と、角の黒い四角は GIF 用に描き足したもの)

グリッドの place-items: center で真ん中に置く

HTML は、真ん中に置きたい要素を body の中に入れるだけです。

html
<body class="center-grid">
  <div class="box">真ん中に置く要素</div>
</body>
css
/* ブラウザが body に付ける既定の余白(8px)を消す */
body {
  margin: 0;
}

/* グリッドで、中の要素を画面の真ん中に置く */
.center-grid {
  display: grid;
  place-items: center; /* 縦と横の両方を中央にそろえる */
  min-height: 100vh; /* dvh を使えないブラウザ向け */
  min-height: 100dvh; /* 画面の高さいっぱいに広げる */
}

place-items は、align-items(縦)と justify-items(横)をまとめて書くプロパティです。値を1つだけ書くと、両方に同じ値が入ります。Chromium で計算値を調べると、align-items と justify-items はどちらも center でした。

幅 300px・高さ 200px の箱を置き、画面の大きさを変えて位置を調べました。

画面の大きさ 箱の左上の位置 左右の余白 上下の余白
800×600 (250, 200) 250px ずつ 200px ずつ
375×667 (37.5, 233.5) 37.5px ずつ 233.5px ずつ

どちらの大きさでも、左右と上下の余白は同じでした。上の GIF のように、画面の大きさを変えている途中でも、箱は真ん中に残ります。

親の高さを画面に合わせないと、縦は真ん中にならない

display: grid と place-items: center だけを書き、min-height を書かずに試すと、箱は横だけ真ん中になり、縦は画面の上の端(0px)にくっつきました。body の高さが、中身の箱と同じ 200px にしかならなかったからです。縦の真ん中にそろえるには、親の高さが画面の高さまで広がっている必要があります。

min-height の代わりに height: 100% を書いた場合も、html に高さを書いていなければ、body の高さは 200px のままでした。height のパーセントの値は、包含ブロック(ここでは html)の高さをもとに決まるので、html と body の両方に height: 100% を書く必要があります。両方に書いて試すと、body の高さは 600px になり、箱は (250, 200) に来ました。ビューポートの高さを直接使える 100vh や 100dvh のほうが、書く場所が1つで済みます。

body { margin: 0; } を書くのにも理由があります。Chromium で調べると、ブラウザが付ける body の既定の余白は 8px でした。この余白を残したまま min-height: 100vh にすると、画面の高さ 600px に対して、文書の高さが 616px になりました。上下の余白の分だけ縦にスクロールできるようになり、箱の位置も 8px 下にずれます。

vh と dvh の違い

<length> の MDN の説明では、vh は大ビューポートの高さ(lvh)と同じ、とされています。スマートフォンのブラウザでアドレスバーが隠れたときの、いちばん大きい画面の高さです。アドレスバーが出ているときは、100vh で大きさを決めた要素の一部が、アドレスバーなどに隠れることがあると書かれています。

dvh は動的ビューポートの単位で、アドレスバーが出たり隠れたりするのに合わせて大きさが変わります。そのため、上のコードでは min-height: 100dvh を使い、その前の行に、dvh を使えないブラウザ向けの min-height: 100vh を置いています。CSS のエラー処理についての MDN の説明にあるとおり、ブラウザは解釈できない宣言を無視します。dvh を知らないブラウザは2つ目の宣言を読み飛ばし、1つ目の 100vh を使います。

デスクトップの Chromium で画面の高さを 600px にして計算値を調べたところ、100vh、100dvh、100svh はどれも 600px でした。アドレスバーが出たり隠れたりするスマートフォンのブラウザでの違いは、実機がないため試していません。

フレックスボックスで書くときは、2つのプロパティを中央にする

フレックスボックスでも同じように置けます。

css
/* フレックスボックスで、中の要素を画面の真ん中に置く */
.center-flex {
  display: flex;
  justify-content: center; /* 横(主軸)を中央に */
  align-items: center; /* 縦(交差軸)を中央に */
  min-height: 100vh;
  min-height: 100dvh;
}

フレックスボックスでは、並べる向き(主軸)の配置を justify-content で、それと直角の向き(交差軸)の配置を align-items で決めます。flex-direction を書かなければ、横に並べる row になります(Chromium で調べた計算値も row でした)。そのため、justify-content が横、align-items が縦になります。

同じ箱で試すと、800×600 でも 375×667 でも、グリッドのときと同じ位置に来ました。

スクロールしても動かない要素は、position: fixed で置く

ダイアログやお知らせのように、ページをスクロールしても画面の真ん中に出し続けたい要素は、position: fixed で画面に固定します。

html
<div class="box fixed-center">真ん中に固定する要素</div>
css
/* スクロールしても動かない、画面の真ん中の要素 */
.fixed-center {
  position: fixed;
  top: 50%;
  left: 50%;
  translate: -50% -50%; /* 自分の幅と高さの半分だけ、左上へ戻す */
}

top: 50% と left: 50% で、要素の左上の角を画面の真ん中に置きます。このままでは、要素は真ん中から右下にずれます。800×600 の画面で translate を書かずに試すと、幅 300px・高さ 200px の箱の左上の角は (400, 300) で、画面の真ん中から右下へ広がっていました。

そこで、translate で要素を左上へ戻します。translate() の MDN の説明にあるとおり、パーセントの値は、横は要素自身の幅、縦は要素自身の高さをもとにします。-50% -50% なら、要素の大きさが分からなくても、ちょうど半分だけ戻せます。

高さ 3000px のページで試すと、箱の左上の位置は 800×600 で (250, 200)、375×667 で (37.5, 233.5) でした。1200px 下までスクロールしても、位置は変わりませんでした。

中身が画面より高いと、上の端が切れることがある

画面全体をおおう層(モーダルの背景など)の真ん中にダイアログを置くときは、層を position: fixed と inset: 0 で画面いっぱいに広げ、その中で中央にそろえます。

css
/* 画面全体をおおう層(モーダルの背景など)の真ん中に置く */
.overlay {
  position: fixed;
  inset: 0; /* top・right・bottom・left をすべて 0 に */
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: auto; /* 中身が画面より高いときはスクロールさせる */
}

中身が小さいうちは、ここまでと同じく真ん中に来ます。困るのは、中身が画面より高くなったときです。高さ 600px の画面で、高さ 900px の箱をこの層の中に置くと、箱の上の端は -150px になりました。箱は真ん中を基準に、上と下へ 150px ずつはみ出します。下にはみ出した分はスクロールで見られますが、いちばん上までスクロールしても、箱の上の端は -150px のままでした。上の 150px は、どうやっても見られません。

2つの画面を縦に並べたもの。上の「align-items: center」では、ダイアログの見出しが画面の上の端で切れて読めない。下の「align-items: safe center」では、ダイアログの見出しから読める
画面より高いダイアログを、いちばん上までスクロールしたときの表示。上が align-items: center、下が align-items: safe center

これを避けるには、center の前に safe を付けます。

css
/* 中身が画面より高くなっても、上の端が切れない */
.overlay-safe {
  align-items: safe center;
}

align-items の MDN の説明では、safe を付けると、要素がはみ出して見えない部分が出るときには、start(上の端)にそろえたかのように置かれる、とされています。.overlay と .overlay-safe の両方を付けて同じように試すと、箱の上の端は 0px になり、300px 下までスクロールして、下の端まで見られました。中身が高さ 200px の箱のときは、safe を付けても真ん中(上の端が 200px)に来ました。

高さ 600px の画面に高さ 900px の箱を置いたときの結果を、書き方ごとに並べます。箱の上の端は、いちばん上までスクロールしたときの位置です。

書き方 箱の上の端 切れるかどうか
.overlay(フレックスボックスと align-items: center) -150px 切れる
.overlay に .overlay-safe(align-items: safe center)を足す 0px 切れない
.overlay で align-items と justify-content を書かず、中の箱に margin: auto を付ける 0px 切れない
層を display: grid と place-items: center で書く 0px 切れない(グリッドの行が箱に合わせて 900px に伸びる。grid-template-rows の計算値は 900px)
.center-grid、.center-flex(min-height で書く) 0px 切れない(body が箱に合わせて 900px まで伸びる)
body をフレックスボックスにして中央にそろえ、height: 100dvh を書く -150px 切れる

切れたのは、フレックスボックスで、親の高さを画面の高さに決めたときでした。親やグリッドの行が中身に合わせて伸びる書き方や、safe か margin: auto を使う書き方では、上の端は切れませんでした。

​Posted by 86