レスポンシブ画像がぼやける原因はsizes属性|枠の実寸を測ってから書く手順とグリッド変更時の見直し
レスポンシブ画像がぼやける原因はsizes属性|枠の実寸を測ってから書く手順とグリッド変更時の見直し
画像を軽くする作業をひととおり終えたあと、こういう相談が来ることがあります。
- 容量は落ちたが、写真が前よりぼやけて見える
- スマホでは綺麗なのに、パソコンで見るとにじむ
- 特定の1枚だけ甘い。他は問題ない
- 「なんか変」としか言えないが、以前と違う
結論から書きます。srcsetを入れた画像がぼやけるとき、原因のほとんどは画像ファイルではなく sizes 属性の宣言値です。
sizes は「用意した画像ファイルの幅」を書く場所ではありません。その画像が画面上で実際に何px幅で表示されるかをブラウザに申告する場所です。ブラウザはこの申告を全面的に信じて、読み込むファイルを決めます。だから申告が実際より小さいと、小さいファイルが選ばれ、それを枠いっぱいまで引き伸ばして表示します。これがぼやけの正体です。
この記事では、枠の実寸の測り方、sizes の書き方、公開後に「引き伸ばされている画像が1枚もない」ことを機械的に確認する手順まで、実測値を交えて書きます。画像の容量を落とす話ではなく、落としたあとに解像度が破綻していないかを保証する話です。
—
1. 結論:sizesは「表示される幅」の宣言。ファイルの幅ではない
まず正しい形を確認します。レスポンシブ画像は3点セットで書きます。
<img src="photo-800.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w"
sizes="(max-width: 768px) 100vw, 560px"
width="800" height="533" alt="店内の様子">
役割は3つです。
| 属性 | 何を書くか | 間違えたときの症状 |
|---|---|---|
srcset |
用意したファイルの実際の横幅(400w = そのファイルが400px) |
候補が足りず、選択肢がない |
sizes |
画面上で表示される枠の幅(CSSの結果) | ぼやける/必要以上に重い |
width/height |
元画像の縦横比(レイアウトずれ防止) | 読み込み中に画面が飛ぶ |
ぼやけの原因になるのは真ん中の sizes です。ここに書くのはCSSで決まる表示幅であって、ファイルの幅ではありません。
ブラウザの動きはこうです。
sizesを読んで「この画像は560px幅で表示されるのだな」と理解する- 端末の画素密度(一般的なスマホやノートPCは2倍)を掛けて、560 × 2 = 1120px の画像が欲しいと判断する
srcsetの候補から1120px以上で最も近いものを選ぶ →photo-1200.jpg
ここで sizes に実際より小さい値、たとえば 272px と書いてあったとします。ブラウザは544px相当の画像で足りると判断し、photo-400.jpg や photo-800.jpg を選びます。ところが実際の枠は560pxある。足りない画像が枠まで引き伸ばされ、輪郭が甘くなります。
重要なのは、ブラウザは実際のレイアウトを見て判断しているわけではないという点です。画像の読み込み判断は、CSSの計算が終わる前に行われます。だからブラウザは書かれた申告を信じるしかない。sizes が嘘をついていても、ブラウザは疑いません。
—
2. なぜこの間違いは見つかりにくいのか
この不具合には、発見を遅らせる性質が3つあります。
エラーが一切出ません。 404にもならず、コンソールにも何も出ません。画像は正常に表示されています。ただ少し甘いだけです。
指摘の言葉が曖昧になります。 「ぼやけている」ではなく「なんか変」という形で来ることが多く、CSSの余白や色を疑って時間を使いがちです。
全部ではなく、一部の画像だけ起きます。 同じレイアウトの中で、幅が変わるカードと変わらないカードが混在していると、症状が出るのは前者だけです。
実際にあった構成で説明します。ある学習塾サイトの制作で、写真8枚をモザイク状に並べたセクションを作りました。カードは基本的に横3列で並びますが、1枚目と最後の1枚だけレイアウト上大きく表示され、幅が約555pxまで広がる設計でした。
このとき8枚すべてに同じ sizes="...272px" が書かれていると、大きく表示される2枚だけが宣言272pxに対して実表示555px=約0.62倍の画像で描かれます。残り6枚は正しいので、パッと見では「全体が悪い」ようには見えません。特定の1枚だけ甘いという、いちばん原因を特定しにくい出方になります。
この構成は美容室やサロンのサイトでもよく使います。ギャラリーで「最初の1枚だけ大きく見せる」レイアウトは定番だからです。「同じ見た目のカードだから同じ sizes でよい」という前提が、レイアウトの例外で崩れると考えてください。
—
3. 手順1:枠の実寸を測る(推測で書かない)
正しく書くための出発点は、表示される枠の幅を実測することです。デザインカンプの数値ではなく、実際にブラウザで描画された幅を測ります。
測り方はブラウザの開発者ツールで十分です。対象の画像を選んで、コンソールで次を実行します。
document.querySelectorAll('img').forEach(img => {
console.log(img.getBoundingClientRect().width, img.currentSrc);
});
getBoundingClientRect().width が、その画像が実際に描かれている幅です。これを測る幅を3つ、最低でも次のポイントで取ります。
- パソコン幅(コンテンツの最大幅。1200pxなど固定値で止まる設計が多い)
- タブレット幅(768〜1024px。ここでレイアウトが切り替わることが多い)
- スマホ幅(375px前後。だいたい画面いっぱい=100vw)
このとき、測る前にCSSの構造を先に確認しておくと早く終わります。コンテンツ幅を決めているのがどのクラスか(.container の max-width なのか、グリッドの列幅なのか)を把握しておけば、測るべきポイントを外しません。ここを飛ばして数値だけ集めると、後でグリッドを変えたときにどこを直せばよいか分からなくなります。
カンプの数値をそのまま sizes に書くのは避けてください。 カンプは1つの画面幅で描かれたものです。余白やgap、コンテナのpaddingが入ると、実際の表示幅はカンプの値と一致しないのが普通です。
—
4. 手順2:sizesをレイアウトの分岐に合わせて書く
実寸が出たら、そのままメディアクエリの形で書き下ろします。
sizes="(max-width: 767px) 100vw,
(max-width: 1023px) 50vw,
380px"
読み方は上から順で、最初に条件が合ったものが採用され、最後は条件なしのデフォルトです。この例は「767px以下では画面幅いっぱい、1023px以下では画面の半分、それより広いときは380px固定」という意味になります。
書くときのポイントは3つです。
固定幅で止まる場所は必ずpxで書く。 コンテンツ幅が1200pxで頭打ちになる設計なら、そこから先は vw ではなく実測pxを書きます。100vw のままにすると、大きなディスプレイで必要以上に大きな画像が読まれ、今度は無駄に重くなります。
例外レイアウトは別の sizes を書く。 先ほどの「1枚目だけ大きい」ケースは、同じ sizes を使い回した時点で破綻します。幅が違うなら属性も違うと割り切って、その画像だけ別に書きます。
gapとpaddingを引く。 3列グリッドだからといって 33vw と書くと、列間のgapとコンテナの左右paddingの分だけ実際より大きくなります。多少のずれは許容できますが、小さめにずれるほうが危険です(小さいとぼやけ、大きいと重くなるだけ)。迷ったら実測値より少し大きめに書きます。
—
5. 手順3:srcsetの候補は実寸から逆算する
sizes が正しくても、srcset に適切なファイルがなければ意味がありません。候補は実表示幅の2倍を上限の目安に用意します。
先ほどの 380px で表示される画像なら、380 × 2 = 760px前後のファイルが最大解像度として必要です。ここに1600pxのファイルしかなければ過剰、400pxしかなければ不足になります。
このとき守るべきルールが1つあります。元より大きく引き伸ばしたファイルを作らないことです。
画像を切り抜いたり調整したりして760pxが734pxになったなら、734pxのまま使い、width と srcset の宣言値も734に合わせます。760に戻そうと拡大リサイズすると、情報量は増えないまま容量だけ増え、画質も落ちます。宣言値は「作ったファイルの実際の幅」に合わせるのが鉄則です。
もう1つ、既存サイトを触るときの注意です。画像の使用箇所を洗い出すとき、src 属性だけを検索すると漏れます。 あるサイトで画像の棚卸しをしたところ、使用中の68点のうち src に書かれたものは48点で、19点は srcset にしか書かれていませんでした。src だけを見ていれば3割近くを見落とす計算です。差し替えや最適化の対象を数えるときは、srcset と CSSの background-image も必ず走査に含めてください。
—
6. 手順4:公開後に「引き伸ばされている画像が1枚もない」ことを確認する
ここが最も重要な工程です。目視では判定できないので、数値で確認します。
確認するのは、実際に読み込まれたファイルの幅 ÷ 表示幅の比です。公開ページで次を実行します。
[...document.querySelectorAll('img')].map(img => {
const shown = img.getBoundingClientRect().width;
const ratio = shown ? (img.naturalWidth / shown) : 0;
return {
src: img.currentSrc.split('/').pop(),
表示幅: Math.round(shown),
実解像度: img.naturalWidth,
倍率: ratio.toFixed(2)
};
}).filter(r => r.倍率 < 1.5).forEach(r => console.table(r));
判定基準はシンプルです。
| 倍率 | 状態 | 対応 |
|---|---|---|
| 1.0未満 | 引き伸ばされている=実害あり | 必ず直す |
| 1.0〜1.5 | 等倍表示。標準的なディスプレイならOKだが、高精細画面では甘い | 主要な画像は直す |
| 2.0前後 | 適正 | そのまま |
| 3.0以上 | 過剰。無駄に重い | 削る候補 |
currentSrc を使う点が肝心です。 src を見ると、ブラウザが実際に選んだファイルではなくフォールバック用の値が返ります。srcset の選択結果は currentSrc にしか出ません。
そしてこの確認はパソコン幅とスマホ幅の両方で実行してください。sizes は画面幅ごとに違う値を返すので、片方で正常でももう片方が壊れていることは普通にあります。
「見た目で大丈夫そう」を判断基準にしないでください。制作者の目は、自分が用意した画像の中身を知っているぶん補正がかかります。 数値で1.0未満がゼロであることを確認して、初めて完了です。
—
7. グリッドを変えたら必ずsizesを見直す
運用上いちばん現実的なリスクがこれです。
sizes はCSSのレイアウトと二重管理になります。 CSSで列数や最大幅を変えても、HTMLの sizes は自動では追従しません。片方だけ変えると、その瞬間から静かにずれます。
具体的に起きやすい変更を挙げます。
- 「3列だと窮屈だから2列に」→ 1枚あたりの幅が広がり、
sizesが過小になる - コンテンツ幅を1140pxから1280pxに広げた→ 全ページの画像が少し引き伸ばされる
- サイドバーを削って本文幅を広げた→ 記事内画像がすべて過小申告になる
どれもCSSだけの変更として扱われがちで、画像は触っていないので誰も確認しません。 そして症状はエラーではなく「少し甘い」なので、しばらく気づかれません。
対策は運用ルールに組み込むことです。
- レイアウト用CSS(列数・最大幅・ブレイクポイント)を変更したら、
sizesの見直しを同じ作業として扱う sizesを書いた箇所に、根拠となった実測値をコメントで残す(例:<!-- PC実測380px / SP 100vw -->)- 公開前チェックに「倍率1.0未満がゼロ」の確認を入れる
3つ目が最終防衛線です。1と2を忘れても、3があれば公開前に止まります。
—
8. 逆方向の失敗も同じ表で見つかる
ここまでは「小さすぎてぼやける」話でしたが、同じ確認方法で「大きすぎて重い」も見つかります。実務ではこちらのほうが件数は多くなります。
ある医療系サイトで表示速度を調べたとき、原因は一様でした。デザインツールから3倍解像度で書き出した画像を、リサイズせずにそのまま設置していたのです。
- メインビジュアルの実表示幅は1345px、設置されていた画像は5379px・7.69MB
- サイト全体で参照されていた画像486枚のうち、388枚(合計115MB)が実表示サイズの2倍を超えていた
デザインツールの「2x」「3x」書き出しは、そのまま設置するためのものではありません。実表示サイズの2倍を上限にリサイズする工程が必要です。
なお、この作業には安全な順番があります。まずファイル名と拡張子を変えない範囲でリサイズと圧縮を行い、それで目標に届くかを実データで判定します。 参照を1箇所も書き換えずに済むため、リンク切れが構造的に起きません。足りない場合に限って、WebPなど拡張子が変わる変換に進みます。逆にすると、参照の書き換え漏れという別の事故を自分から作ることになります。
そしてリサイズしたら width と srcset の宣言値も一緒に直します。ここを忘れると、今度は本記事の前半で書いたぼやけが発生します。容量を削る作業と解像度の宣言はセットです。
—
9. 公開前チェックリスト
画像まわりを触ったときに、この5つだけ確認すれば大きな事故は防げます。
- 枠の実寸を測ったか(カンプではなくブラウザで描画された幅)
sizesに実表示幅を書いたか(ファイルの幅ではない。固定幅で止まる場所はpxで書く)- 例外レイアウトの画像に別の
sizesを書いたか(1枚だけ大きい、などの構成) - 公開後に倍率を測り、1.0未満がゼロか(PC幅とスマホ幅の両方で)
- リサイズしたファイルの
width・srcsetの宣言値を実寸に合わせたか
サイトの更新を自社で行っている場合は、4だけでも運用に入れておく価値があります。倍率1.0未満は「画質が落ちている」という事実そのもので、誰が確認しても同じ答えが出ます。
—
まとめ
- srcsetを入れた画像がぼやける原因は、ほぼ
sizesの宣言ミス。ファイルの幅ではなく表示される枠の幅を書く - ブラウザは宣言を疑わない。実際の枠より小さく申告すると、小さい画像が引き伸ばされる
- 症状はエラーにならず「なんか変」としか出ないため、数値で確認しないと発見できない
- 確認方法は
currentSrcの解像度 ÷ 表示幅。1.0未満が1枚でもあれば実害。PC幅とSP幅の両方で測る - 同じレイアウトでも幅が変わる例外の画像(1枚だけ大きいカードなど)が最初に壊れる
sizesはCSSと二重管理。列数・最大幅・ブレイクポイントを変えたら必ず見直す- 逆に実表示の2倍を大きく超える画像は無駄な重さ。同じ確認表で洗い出せる
画像の最適化は「軽くする」だけでは終わりません。軽くした結果、必要な解像度を割っていないかまで確認して、はじめて完了です。確認は数分で終わり、一度手順を決めれば毎回同じ作業になります。感覚ではなく数字で判定できる状態を作っておくことをおすすめします。
ホームページの画像や表示で気になる点がある場合は、実際のページを実測したうえで、どこに手を入れるべきかをお伝えできます。お気軽にご相談ください。