CSSのメディアクエリ入門:画面幅で変わるレイアウトを図とサンプルで解説
CSSメディアクエリを図と操作サンプルで解説。min-width・max-width、1列から3列への変化、境界と優先順位、ダークモード・動きを減らす設定・印刷対応まで学べます。
メディアクエリは、画面の幅や向き、利用者の表示設定などの条件に応じてCSSを適用する仕組みです。@media を使い、同じHTMLを狭い画面では1列、広い画面では2列・3列へ変える例から学びます。端末名を判定するのではなく、利用できる表示領域に合わせて設計することが大切です。
メディアクエリで表示はどう変わるか
同じHTMLを1列・2列・3列にする
狭い画面に3列を詰め込むと、文章が短い間隔で折り返されて読みにくくなります。そこで、まず1列の読みやすい表示を作り、余裕が生まれたら列を増やします。この例の640pxと960pxは学習用の値で、すべてのサイトに共通する正解ではありません。
境界の直前と直後を試す
390pxだけでなく、639px・640px・641px、959px・960px・961pxを比べられます。枠内は独立したページで、そのビューポートの幅を変更します。JavaScriptは枠の幅を変えるだけで、列数は実際のCSSメディアクエリが決めます。広い幅を選んだ場合は、サンプル枠を横へスクロールして確認できます。
@mediaの基本構文
条件に一致した宣言だけが候補になる
.cards {
display: grid;
grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 640px) {
.cards {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}@media の後ろが条件、その中が条件成立時のCSSです。幅が640px以上なら2列の宣言が適用候補になります。幅が狭くなれば、その条件の宣言は候補から外れ、基本の1列へ戻ります。JavaScriptでクラスを付け替える必要はありません。
メディアクエリ自体がセレクターの詳細度を高くするわけではありません。別の規則の詳細度や !important、カスケードレイヤーなどの影響も受けます。
viewportの指定を忘れない
<meta name="viewport" content="width=device-width, initial-scale=1">index.html の head に入れます。これがないと、モバイルブラウザーが広いレイアウト用ビューポートを使い、意図した幅の条件で表示されない場合があります。拡大を妨げる user-scalable=no などは追加せず、文字を拡大しても使えるようにしてください。
min-width・max-width・範囲構文の違い
min-widthは指定幅以上、max-widthは指定幅以下
/* 640pxちょうどを含む */
@media (min-width: 640px) {
.cards { gap: 24px; }
}
/* 640pxちょうどを含む */
@media (max-width: 640px) {
.page-title { font-size: 24px; }
}両方とも境界を含むので、幅640pxでは両条件が成立します。別々のプロパティなら問題ありませんが、同じプロパティを互いに反対の値へ変える設計では、境界の重複に注意します。width はCSSピクセルで評価され、端末の物理ピクセル数とは同じではありません。
範囲構文なら境界を直接表せる
@media (width < 640px) {
.page-title { font-size: 24px; }
}
@media (640px <= width < 960px) {
.page-title { font-size: 30px; }
}
@media (width >= 960px) {
.page-title { font-size: 36px; }
}不等号を使う範囲構文は、「未満」と「以上」を直接表せます。古いブラウザーまで対象にする場合は、従来の min-width・max-width と範囲構文を同じ対応範囲だと考えず、互換性表を確認します。このページの完成サンプルは、基本の1列へ min-width で上書きする構成です。
モバイルファーストとブレークポイント
狭い画面の基本形から、必要な箇所だけ変える
.cards { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 640px) {
.cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 960px) {
.cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}画面が広くなったときの変更を下へ追加していく考え方です。デスクトップ向けの複雑な配置を後から狭い画面用に打ち消す量を減らせます。一方、既存サイトの設計によっては max-width を中心にした方が変更を小さくできる場合もあります。
端末名ではなく、内容が窮屈になる幅で決める
「スマートフォンは必ずこの幅」と決めつけず、ウィンドウを少しずつ狭めて、見出し・ナビゲーション・カードの文章が読みにくくなる地点を探します。画面分割やブラウザーの拡大でも利用可能な幅は変わります。640px・960pxは、このカード例に対する設計上の区切りです。
ブレークポイントを em で書くこともできます。メディアクエリ内のフォント相対単位は、要素へ指定した font-size ではなく初期のフォントサイズに基づくため、「常に1emは16px」と固定して考えないでください。
複数の条件が成立したときの優先順位
960pxでは2列と3列の両方が候補になる
幅960px以上では、640px以上という条件も満たします。サンプルは同じ .cards セレクターで、同じレイヤー・重要度の宣言なので、後に書いた3列の指定が勝ちます。「条件がより狭い方が自動で優先される」という規則はありません。
条件の組み合わせはand・カンマ・notで表す
/* 両方を満たすとき */
@media (min-width: 640px) and (orientation: landscape) {
.toolbar { display: flex; }
}
/* どちらかを満たすとき */
@media (max-width: 480px), (orientation: portrait) {
.toolbar { gap: 8px; }
}
/* screenというメディア型ではないとき */
@media not screen {
.screen-only { display: none; }
}and は条件の両立、カンマは複数クエリのいずれかの一致を表します。複雑な条件は括弧と否定の範囲を確認し、読みやすさを優先してください。メディア型を省略した場合は基本的に all として扱われます。
画面幅以外によく使う条件
orientationは表示領域の縦横比
@media (orientation: landscape) {
.preview { aspect-ratio: 16 / 9; }
}landscape はビューポートが横長、portrait は縦長または正方形の状態です。端末の物理的な向きを直接読み取るセンサーではありません。ソフトウェアキーボードで表示領域が変わる場合も考慮します。
hoverとpointerは操作の特性
@media (hover: hover) and (pointer: fine) {
.button:hover { text-decoration: underline; }
}
@media (pointer: coarse) {
.button { min-height: 48px; padding: 12px 20px; }
}
.button:focus-visible {
outline: 3px solid #a34b29;
outline-offset: 4px;
}hover は主な入力機構がホバーできるか、pointer は主なポインターの精度を表します。タッチ対応PCなど複数の入力があるため、単純なスマートフォン判定には使いません。いずれかの入力機構を調べる any-hover・any-pointer もあります。操作の必須情報をホバー時だけに隠さず、キーボードのフォーカスにも対応します。
prefers-color-schemeで配色の希望に合わせる
:root {
color-scheme: light dark;
--surface: #ffffff;
--text: #233a32;
}
@media (prefers-color-scheme: dark) {
:root {
--surface: #17251e;
--text: #edf5e3;
}
}
body { background: var(--surface); color: var(--text); }背景だけでなく文字・境界線・リンク・フォームも確認します。独自のテーマ切り替えを設ける場合は、利用者が選んだテーマとOSの希望のどちらを優先するかも決めます。完成サンプルはOS・ブラウザーの設定に従います。
prefers-reduced-motionで装飾的な動きを抑える
.card { transition: transform 180ms ease; }
.card:hover { transform: translateY(-4px); }
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
animation: none;
}
.card:hover { transform: none; }
html { scroll-behavior: auto; }
}動きを減らす設定を尊重し、不要な移動や点滅を止めます。この例は学習用の追加例で、完成サンプルには最初からアニメーションを設けていません。サイト全体へ一律の極端に短いアニメーション時間を設定するより、動きの役割を見て対象を指定すると、完了イベントなどへの影響を整理しやすくなります。
printで印刷向けに整える
@media print {
.site-navigation, .screen-only { display: none; }
body { color: black; background: white; }
.card { break-inside: avoid; }
}印刷ではナビゲーションなどを省き、読める色と改ページを考えます。break-inside: avoid は紙面に収まらない巨大な要素まで必ず1ページへ収める保証ではありません。実際の印刷プレビューで切れ方を確認してください。
コピーして動かせる完成例
index.htmlに3枚のカードを置く
次のHTMLを index.html として保存し、同じフォルダーへ styles.css を作ります。JavaScriptを使わずに画面幅で表示が変わります。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>メディアクエリの練習</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<h1>学習テーマ</h1>
<div class="cards">
<section class="card"><h2>HTML</h2><p>文章の意味と構造を作ります。</p></section>
<section class="card"><h2>CSS</h2><p>余白・色・レイアウトを整えます。</p></section>
<section class="card"><h2>JavaScript</h2><p>ユーザーの操作に応じて画面を動かします。</p></section>
</div>
</body>
</html>styles.cssを保存する
以下は公開サンプルと共通のCSSです。末尾にはダークモードと印刷用の指定も含めています。.demo-status は公開サンプルの測定表示だけを印刷時に隠す指定で、上のHTMLにその要素がなくても問題ありません。
* { box-sizing: border-box; }
body {
margin: 0;
padding: 20px;
font: 16px/1.8 system-ui, sans-serif;
color: #233a32;
background: #f8faf3;
}
h1 { font-size: 24px; }
.cards {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 16px;
}
.card {
padding: 20px;
border: 1px solid #6d865e;
border-radius: 12px;
background: #fff;
overflow-wrap: anywhere;
}
.card h2 { margin: 0 0 8px; font-size: 20px; }
.card p { margin: 0; }
/* 640px以上では2列 */
@media (min-width: 640px) {
.cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* 960px以上では、この後の指定が優先されて3列 */
@media (min-width: 960px) {
.cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (prefers-color-scheme: dark) {
body { color: #edf5e3; background: #17251e; }
.card { background: #24362b; border-color: #b6cb96; }
}
@media print {
body { padding: 0; color: #000; background: #fff; }
.cards { display: block; }
.card { margin-bottom: 12px; break-inside: avoid; color: #000; background: #fff; }
.demo-status { display: none; }
}期待する表示を確認する
- 390px:カードが1列に並ぶ。
- 639px:まだ1列。
- 640px:2列になる。3枚目は次の行へ移る。
- 959px:2列のまま。
- 960px:3列になる。
- 1200px:3列のまま、利用できる幅が増える。
メディアクエリが効かないとき
確認する順番
styles.cssのパスと読み込み結果を開発者ツールのNetworkで確認する。viewportの指定がheadにあるか確認する。- 対象要素とセレクターが一致しているか確認する。
@mediaの括弧・波括弧・単位を確認する。- Stylesで条件が成立しているか、宣言が打ち消されていないかを見る。
- Computedで最終的な列数や幅を調べる。
- 拡大表示・スクロールバー・iframe内の表示など、想定と実際のビューポートが違わないか確認する。
matchMediaで条件の一致を調べる
const query = window.matchMedia('(min-width: 640px)');
console.log(query.matches);
query.addEventListener('change', event => {
console.log('640px以上か:', event.matches);
});ブラウザーのConsoleで実行できます。window.matchMedia() はブラウザーのAPIで、通常のNode.jsにそのまま存在するものではありません。見た目を変えるだけならCSSを優先し、JavaScriptはアプリの処理自体を条件に連動させたいときに使います。
コンテナクエリとの使い分け
メディアクエリは表示環境、コンテナクエリは部品の入れ物
画面が広くても、サイドバー内のカードは狭いことがあります。部品が置かれた入れ物の幅で切り替えるなら、サイズコンテナクエリの @container が候補です。ページ全体の段組みにはメディアクエリ、再利用する部品の領域にはコンテナクエリというように、判定したい基準を選びます。
ブラウザー互換性と参考資料
@media全体と個別機能を分けて確認する
幅による従来のクエリ、範囲構文、利用者設定の条件は、それぞれ対応開始版が異なります。@media が使えることだけで全条件に対応すると判断せず、対象ブラウザーと各機能の互換性表を確認してください。認識されない条件に依存して必須情報を表示する設計は避け、基本の表示だけでも読めるようにします。