AI先生のロボットキャラクター
第4章 - セクション7

レスポンシブサイトのパフォーマンス最適化

画像・CSS・描画の3層から性能低下の原因を理解し、srcset/CSS変数/transform最適化など実践的な代替策を実装できるようになります。

男子生徒のアイコン

レスポンシブ対応してからサイトが重くなった気がするんですが、何が原因なんでしょう?

AI先生のアイコン

レスポンシブ化したときに起きやすいパターンがあるよ。画像のサイズがそのまま、CSSが増え続ける、アニメーションが重くなる、この3つが特によく見かける原因だ。

女子生徒のアイコン

CSSが増えるのはわかるんですが、画像の問題はどういうことですか?

AI先生のアイコン

スマホでも2000px幅の大きな画像をそのまま配信してしまうことがある。CSSで縮小表示していても、ファイルサイズは変わらないからね。今日はこの3つの問題を順に辿って、正しい代替策と実測確認まであわせて学ぼう。

パフォーマンス低下の3レイヤー

レスポンシブサイトの速度低下は「画像の転送量が多い」「CSSの解析に時間がかかる」「描画処理が重い」の3層で起きます。どれか1つを直せばよいのではなく、それぞれが連動しています。

パフォーマンス低下の3レイヤー
まずはどこが重いかを層ごとに切り分けます
画像転送の負荷
高解像度画像を全端末へ配信すると通信量が急増し、初回表示が遅くなります。
CSS解析の負荷
重複したメディアクエリや不要ルールが増えると、スタイル計算に時間がかかります。
描画処理の負荷
topやleftのアニメーションは再レイアウトを誘発し、スクロール中のカクつきにつながります。
最初に計測して優先順位を決める
同じ最適化でも効果はサイトごとに異なるため、改善前後の数値比較が必須です。

全体像と優先順位

実際には「9割が画像の問題」というサイトも珍しくありません。最初に計測して、どの層のインパクトが一番大きいかを確認してから着手するのが最も効率的です。

最優先すべき順序を整理すると、次のようになります。

  • 計測(計測ファースト)
    • まずブラウザの開発ツールやLighthouseで現状を数値化します
  • 画像の最適化
    • 転送量削減の効果が最も大きいケースが多いため優先します
  • CSS肥大化の解消
    • @mediaの重複や不要ルールを整理して解析コストを下げます
  • 描画処理の改善
    • アニメーションやレイアウトシフトを引き起こすプロパティを確認します
  • 再計測
    • 改善後に数値を確認して効果を検証します
最適化の進め方(5ステップ)
順番を固定すると手戻りを減らせます
STEP
01
STEP 1 計測
LighthouseやDevToolsで、最初のボトルネックを特定します。
STEP
02
STEP 2 画像最適化
srcsetとsizesで端末に合う画像を配信し、転送量を先に削減します。
STEP
03
STEP 3 CSS整理
重複する@mediaと不要ルールを減らし、解析コストを下げます。
STEP
04
STEP 4 描画改善
top/left中心の動きをtransformへ置換し、再レイアウトを避けます。
STEP
05
STEP 5 再計測
改善後の指標を再確認し、効果が低い項目は再調整します。
計測なし最適化の危険

「このコードが重いはずだ」という思い込みで作業を進めると、実際にはほとんど効果のない最適化に時間をかけることになります。必ず数値でインパクトの大きい部分から始めましょう。

画像最適化の実装

CSSだけで縮小表示しても画像転送コストは下がらないため、HTML側で画像指定を変える必要があります。srcset / sizes / picture / loading="lazy" を組み合わせた現代的な実装を確認しましょう。

NG: 単一src配信
すべての端末が同じ大画像を受け取る
PC 1920px: hero.jpg (2000w / 2.1MB)
Tablet 1024px: hero.jpg (2000w / 2.1MB)
Mobile 390px: hero.jpg (2000w / 2.1MB)
表示は小さくても通信量は減らない
OK: srcset + sizes
端末幅ごとに最適サイズを選択
PC 1920px: hero-1200w.jpg (420KB)
Tablet 1024px: hero-800w.jpg (240KB)
Mobile 390px: hero-600w.jpg (120KB)
端末に合わせて転送量を最小化できる

picture要素でフォーマットを切り替える

WebP形式は同品質のJPEGより30〜40%ほど小さいケースが多く、サポートされているブラウザに優先して配信できます。<picture> 要素を使うと、ブラウザのサポート状況に応じたフォールバックを記述できます。

女子生徒のアイコン

WebPって何ですか?

AI先生のアイコン

Googleが開発した画像フォーマットだよ。JPEGより圧縮効率がよくて、同じ見た目でファイルが小さい。ただ古いブラウザには対応していないから、fallbackとしてJPEGも用意しておくのがポイント。

pictureによるWebP+JPEGフォールバック
<picture>
    <source
        type="image/webp"
        srcset="hero-600w.webp 600w, hero-1200w.webp 1200w"
        sizes="(max-width: 600px) 100vw, 80vw">
    <img
        src="hero-1200w.jpg"
        srcset="hero-600w.jpg 600w, hero-1200w.jpg 1200w"
        sizes="(max-width: 600px) 100vw, 80vw"
        alt="ヒーロー画像"
        loading="lazy"
        width="1200"
        height="675">
</picture>

<source>type="image/webp" を指定すると、WebP対応ブラウザはそちらを、非対応ブラウザは <img>srcset を使います。

loading=“lazy” と width/height 属性

loading="lazy" をつけると、画面外にある画像はビューポートに近づいてから読み込まれます。ファーストビュー外の画像には積極的に活用します。

widthheight 属性は、画像が読み込まれる前からブラウザが適切なスペースを確保するために重要です。これがないと、画像ロード後にページ全体がずれる「レイアウトシフト(CLS)」が発生します。

プレビュー:

実際のプロジェクトでは srcset のURLを実際の画像パスに替えてください。ビルドツール(Vite、AstroのImage機能など)を使うと最適化後の画像を自動生成できます。

CSS最適化の実装

画像以外で改善できるのが、CSS側の肥大化と描画コストです。ここではCSS変数による整理と、content-visibility を使ったレンダリング最適化を扱います。

CSS変数で@mediaを1か所に集約する

@media の条件が各セレクターにバラバラに書かれていると、数値の変更時に全体を検索して直す必要があります。レスポンシブ対応値をCSS変数に集約し、変数の切り替えを @media の1か所だけで行う設計にすると、管理コストが大きく下がります。

男子生徒のアイコン

CSS変数ってメディアクエリの条件値にも使えるんですか?

AI先生のアイコン

使えないんだ。@media (max-width: var(--bp)) は動かない。だから「条件の値は直書き」して、条件の中身、つまり変更するプロパティの値だけを var() で受け取る、という分担にするんだよ。

女子生徒のアイコン

条件は直書きで、中のスタイルは var() に任せる、ということですね。

AI先生のアイコン

そういうこと。こうすると「640pxを超えたら変数の値を上書きする」という形で、全体のサイズ感を一気に変えられる。

CSS変数を使ったレスポンシブ整理
:root {
    --font-heading: clamp(1.4rem, 4vw, 2.2rem);
    --font-body: clamp(0.9rem, 2vw, 1rem);
    --space-section: clamp(24px, 5vw, 48px);
    --grid-cols: 1;
    --card-radius: 12px;
}

@media (min-width: 640px) {
    :root {
        --grid-cols: 3;
    }
}

.lp-page {
    font-family: sans-serif;
    max-width: 900px;
    margin: 0 auto;
    padding: var(--space-section);
    background: #f8fafc;
}

.page-header {
    text-align: center;
    margin-bottom: var(--space-section);
}

.headline {
    font-size: var(--font-heading);
    color: #1e293b;
    margin: 0 0 8px;
}

.subline {
    font-size: var(--font-body);
    color: #64748b;
    margin: 0;
}

.feature-section {
    display: grid;
    grid-template-columns: repeat(var(--grid-cols), 1fr);
    gap: 16px;
}

.feature-card {
    background: #fff;
    border-radius: var(--card-radius);
    padding: 24px;
    text-align: center;
    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}

.feature-card i {
    font-size: 2rem;
    color: #10b981;
    margin-bottom: 12px;
    display: block;
}

.feature-card h2 {
    font-size: 1rem;
    margin: 0 0 8px;
    color: #1e293b;
}

.feature-card p {
    font-size: 0.85rem;
    color: #64748b;
    margin: 0;
}

clamp() を使うと最小値・推奨値・最大値の範囲で自動的にサイズが変わるため、ブレークポイントの数を減らしながら滑らかな対応が実現できます。

プレビュー:

プレビューの幅を変えると、640px境界でグリッドが1列から3列に切り替わることが確認できます。

描画処理の負荷を下げる

描画処理の重さは、ざっくり「レイアウト計算」「再描画(ペイント)」「合成(コンポジット)」の3段階で決まります。特に top / left / width などをアニメーションで動かすと、レイアウト計算が繰り返し発生してカクつきやすくなります。

レスポンシブサイトでは、スクロール中やボタンホバー時の小さな動きでも負荷が積み上がるため、動きは transform / opacity を中心に設計するのが基本です。

描画負荷を増やしやすい書き方と改善例
/* 負荷が高くなりやすい例: レイアウトを毎フレーム再計算 */
.cta-button-bad {
    position: relative;
    left: 0;
    transition: left 0.25s ease;
}

.cta-button-bad:hover {
    left: 8px;
}

/* 改善例: コンポジット中心で処理 */
.cta-button-good {
    transition: transform 0.25s ease, opacity 0.25s ease;
}

.cta-button-good:hover {
    transform: translateX(8px);
    opacity: 0.92;
}

この考え方を先に押さえておくと、後半の誤用パターン(top/leftアニメーション)も「なぜ避けるべきか」が理解しやすくなります。

content-visibility で描画外の要素をスキップする

ページが長くなると、スクロールしていない部分のレイアウト計算が初期表示の速度を下げることがあります。content-visibility: auto をセクションに設定すると、ビューポート外のレンダリング計算を必要なタイミングまで遅延できます。

content-visibilityで描画外セクションをスキップ
/* 長いランディングページの各セクションに適用 */
.lp-section {
    content-visibility: auto;
    /* ジャンプ防止のためおおよその高さを指定する */
    contain-intrinsic-size: 0 600px;
}

contain-intrinsic-size を省くと、スクロールバーの高さが突然変わることがあります。おおよその高さを設定しておくと違和感が出ません。

運用規模別の優先順位の決め方

同じ最適化でも、サイトの運用形態によって優先順位は変わります。ここを先に決めると、無駄な作業を減らせます。

  • 小規模の静的サイト(個人制作・ページ数が少ない)
    • 画像最適化を最優先にし、CSSは重複削減までで十分なことが多いです
  • CMS主体サイト(記事ページが大量に増える)
    • 画像の自動変換・自動リサイズの仕組みを先に作り、運用時の手作業をなくします
  • チーム開発案件(複数人で継続改修)
    • @media 条件値の統一ルール、レビュー観点、計測手順をドキュメント化して再発を防ぎます

誤用パターンと代替策

全体像と実装の基礎を押さえたうえで、最後に見落としやすい誤用を4つ確認します。

誤用1:CSSリサイズだけで元画像は大きいまま

max-width: 100% でCSSによる縮小表示はできますが、ブラウザが実際にダウンロードするファイルサイズはそのままです。スマホが2MB超の画像を受け取ることになります。

CSSリサイズのみ(NG):HTML
<!-- 元画像が2000×1200px / 2.1MB でもCSSで縮小するだけでは全デバイスが2.1MBを受け取る -->
<img src="hero.jpg" alt="ヒーロー画像">
CSSリサイズのみ(NG):CSS
.hero img {
    max-width: 100%;
    height: auto;
}

代わりに srcsetsizes を使って画像自体を複数用意し、画面幅に応じたファイルを配信します。

srcset/sizesで最適サイズを配信(OK)
<img
    src="hero-600w.jpg"
    srcset="hero-600w.jpg 600w, hero-1200w.jpg 1200w, hero-2000w.jpg 2000w"
    sizes="(max-width: 600px) 100vw, (max-width: 1200px) 80vw, 1200px"
    alt="ヒーロー画像"
    loading="lazy"
    width="1200"
    height="675">

誤用2:display:none による非表示の落とし穴

CSSで display: none をつけても、imgsrc 属性がHTMLに存在すれば多くのブラウザは画像をダウンロードします。DOMにノードが残っている限り、HTMLの解析コストも発生します。

display:none で非表示(NG)
/* スマホから見えないようにしたが、画像は読み込まれてしまう */
.desktop-only {
    display: none;
}

@media (min-width: 768px) {
    .desktop-only {
        display: block;
    }
}

モバイルで不要なコンテンツは、基本的にHTML自体を出力しない方針に切り替えます。display: nonehidden で隠すだけでは、読み込みコストが残るケースがあるためです。

誤用3:@mediaが各セレクターにバラバラに記述される

同じ条件値の @media がファイル内に散在すると、数値を変更するたびに全箇所を検索して直す必要が出ます。特に複数人で開発するプロジェクトでは、誤って同じ @media を二重に書いてしまうケースが増えやすく、デバッグに時間がかかります。

@mediaの重複パターン(NG)
.hero { font-size: 2rem; padding: 32px; }
.card { margin-bottom: 24px; }

@media (max-width: 768px) { .hero { font-size: 1.4rem; padding: 20px; } }
@media (max-width: 768px) { .card { margin-bottom: 16px; } }

/* 同じ条件が各所に散らばり、変更時に漏れが発生しやすい */

誤用4:アニメーションにtop/leftを使う

topleft などの位置プロパティを変化させるアニメーションは、ブラウザがレイアウト再計算を毎フレーム行います。transform: translate() に置き換えることでGPUを使ったコンポジットアニメーションになり、CPU負荷を大幅に抑えられます。

top/leftアニメーションと代替比較
/* NG:topを変えるとレイアウト再計算が毎フレーム発生する */
.item-ng {
    position: absolute;
    top: 0;
    transition: top 0.3s ease;
}
.item-ng:hover {
    top: -8px;
}

/* OK:transformはGPUコンポジット層で処理される */
.item-ok {
    position: relative;
    transition: transform 0.3s ease;
}
.item-ok:hover {
    transform: translateY(-8px);
}
will-changeの注意点

will-change: transform は、アニメーションが始まる直前に設定し、終わったら外すのが理想です。全要素に常時設定するとGPUメモリを常に消費し、逆効果になることがあります。

理解度チェッククイズ

AI先生のアイコン

最後に確認しておこう。ここで習ったことを5問で試してみて。

パフォーマンス最適化クイズ

srcsetのwディスクリプタ(例: 600w)は何を表しますか?
表示する幅のCSS値
画像ファイルの実際の横幅(px)
ウィンドウ幅に対する割合
圧縮率のレベル
loading="lazy"を指定した画像はいつ読み込まれますか?
ページのHTMLが解析されたとき
JavaScriptが実行されたとき
ビューポートに近づいたとき
ユーザーが画像をクリックしたとき
CSS変数は@mediaの条件式の中(例: @media (max-width: var(--bp)))で使えますか?
使える
使えない
一部のブラウザだけ使える
min-widthのみ使える
display:noneでモバイル向けに非表示にしたとき、その要素内の画像は読み込まれますか?
読み込まれない(描画しないのでスキップされる)
多くのブラウザで読み込まれる
Safariだけ読み込まれる
画像サイズが1MB以上の場合のみ読み込まれる
アニメーションをGPUコンポジット層で処理させるには、どのプロパティを使うべきですか?
top / left
margin-top / margin-left
transform / opacity
font-size / padding

まとめ

女子生徒のアイコン

今日ってけっこう範囲が広かったですね。画像とCSSとアニメーションって全部別の話に見えて。

AI先生のアイコン

そうだね。ただ共通しているのは、見た目が変わらなくてもブラウザが余計な作業をしているケースがある、ということ。計測して問題を絞ってから直す、という手順はどれも同じだよ。

男子生徒のアイコン

srcsetとsizesはまだちょっと難しい感じがするんですが。

AI先生のアイコン

最初はそうだよ。小さな画面で実際に数パターン書いて比べると、かなり早く慣れていくよ。

女子生徒のアイコン

CSS変数で@mediaをまとめるのはすごく使えると思いました。今まで同じ数字を何か所にも書いてたので。

AI先生のアイコン

それが一番身近でインパクトが大きいかもね。コードの量が減るし、修正ミスも減る。計測でどこが遅いかを確認してから、今日の手順を当てはめてみて。

パフォーマンス最適化のポイント
  • 計測ファースト
    • Lighthouseや開発ツールで現状の数値を確認してから行動します
  • srcset + sizes + loading=“lazy”
    • 画像最適化はまずこの3つから始めます
  • width / height 属性でCLSを防ぐ
    • 画像領域を事前に確保することで読み込み後のレイアウトシフトをなくします
  • CSS変数で@media一元管理
    • 条件の値は直書き、プロパティの値をvar()で受け取る設計にします
  • アニメーションはtransform / opacity
    • top / leftを動かすとレイアウト再計算が毎フレーム発生します

学習チェック

このレッスンを理解できたら「完了」をクリックしてください。
後で見直したい場合は「未完了に戻す」で進捗をリセットできます。

レッスン完了!🎉

お疲れさまでした!