2026年5月17日

2026年5月17日

WordPressで画像の遅延読み込み(Lazy Load)を設定する方法

はじめに

「ページに大量の画像があって初期読み込みが遅い」「PageSpeed Insightsで画面外の画像を遅延読み込みするよう指摘された」——遅延読み込みを正しく設定すると初期ページロード時間を大幅に短縮でき、特にモバイルでの体験が向上します。

症状・原因

WordPress 5.5以降、wp_get_attachment_image()などの関数が出力するタグには自動でloading="lazy"が付与されます。ただし、LCP(最大コンテンツ描画)に使われるアイキャッチ画像にlazyが付いていると逆効果になるため、適切に制御する必要があります。

解決手順

ステップ1:WordPress標準の遅延読み込みを確認・制御する

// WordPress 5.5以降、img要素にloading="lazy"が自動付与される
// the_post_thumbnail()、wp_get_attachment_image() などが対象

// LCP画像(ファーストビューの最大画像)には"eager"を設定する
the_post_thumbnail( 'large', [
    'loading'         => 'eager',      // 遅延読み込みを無効化
    'fetchpriority'   => 'high',       // 優先読み込み(LCP改善)
    'decoding'        => 'async',
] );

// アーカイブページのサムネイルは最初の1枚だけeager、残りはlazy
$post_index = 0;
while ( have_posts() ) : the_post();
    $loading = ( $post_index === 0 ) ? 'eager' : 'lazy';
    the_post_thumbnail( 'medium', [
        'loading'       => $loading,
        'fetchpriority' => ( $post_index === 0 ) ? 'high' : 'auto',
    ] );
    $post_index++;
endwhile;
// functions.php: 特定条件でloading属性を制御
add_filter( 'wp_lazy_loading_enabled', function( $default, $tag_name, $context ) {
    // 投稿アイキャッチにはlazyを適用しない(LCP対策)
    if ( 'img' === $tag_name && 'the_post_thumbnail' === $context ) {
        return false;
    }
    return $default;
}, 10, 3 );

// 個別の画像タグのloading属性を変更
add_filter( 'wp_get_attachment_image_attributes', function( $attr, $attachment, $size ) {
    // ヒーロー画像としてよく使われるサイズはeagerに
    if ( in_array( $size, [ 'full', 'hero-image' ], true ) ) {
        $attr['loading']       = 'eager';
        $attr['fetchpriority'] = 'high';
    }
    return $attr;
}, 10, 3 );

ステップ2:コンテンツ内の画像すべてにlazyを適用する

// functions.php: the_content()の画像にloading="lazy"を追加
add_filter( 'the_content', function( $content ) {
    if ( is_admin() ) return $content;

    // すでにloading属性がある画像はスキップ
    $content = preg_replace_callback(
        '/<img(?![^>]*loading=)([^>]*)>/i',
        function( $matches ) {
            return '<img loading="lazy" decoding="async"' . $matches[1] . '>';
        },
        $content
    );

    return $content;
} );

ステップ3:Intersection Observer APIでカスタム遅延読み込みを実装する

// js/lazy-load.js: Intersection Observer によるカスタム遅延読み込み
// data-src属性に実際のURLを入れておき、表示エリアに入ったら src に移す

(function() {
    'use strict';

    // Intersection Observer が使えない環境ではすべて即座に読み込む
    if (!('IntersectionObserver' in window)) {
        document.querySelectorAll('img[data-src]').forEach(function(img) {
            img.src = img.dataset.src;
            if (img.dataset.srcset) img.srcset = img.dataset.srcset;
        });
        return;
    }

    const observer = new IntersectionObserver(function(entries) {
        entries.forEach(function(entry) {
            if (!entry.isIntersecting) return;

            const img = entry.target;
            img.src = img.dataset.src;

            if (img.dataset.srcset) {
                img.srcset = img.dataset.srcset;
            }
            if (img.dataset.sizes) {
                img.sizes = img.dataset.sizes;
            }

            img.classList.add('loaded');
            img.removeAttribute('data-src');
            observer.unobserve(img);
        });
    }, {
        rootMargin: '200px 0px', // 200px手前から読み込み開始
        threshold:  0.01,
    });

    document.querySelectorAll('img[data-src]').forEach(function(img) {
        observer.observe(img);
    });
})();
// functions.php: コンテンツ画像のsrcをdata-srcに変換
add_filter( 'the_content', function( $content ) {
    if ( is_admin() || is_feed() ) return $content;

    // ファーストビューの画像はスキップ(最初の1枚)
    $first = true;

    $content = preg_replace_callback(
        '/<img([^>]*)>/i',
        function( $matches ) use ( &$first ) {
            if ( $first ) {
                $first = false;
                return $matches[0]; // 最初の画像はそのまま
            }

            $attrs = $matches[1];

            // srcをdata-srcに変換
            $attrs = preg_replace( '/\ssrc=(["\'])([^"\']*)\1/i', ' data-src=$1$2$1', $attrs );
            $attrs = preg_replace( '/\ssrcset=(["\'])([^"\']*)\1/i', ' data-srcset=$1$2$1', $attrs );

            // クラスにlazy-imgを追加
            if ( preg_match( '/\sclass=(["\'])([^"\']*)\1/i', $attrs, $cls ) ) {
                $attrs = str_replace( $cls[0], ' class=' . $cls[1] . $cls[2] . ' lazy-img' . $cls[1], $attrs );
            } else {
                $attrs .= ' class="lazy-img"';
            }

            return '<img' . $attrs . '>';
        },
        $content
    );

    return $content;
} );

ステップ4:iframe(動画埋め込み)の遅延読み込み

// functions.php: iframeにloading="lazy"を追加
add_filter( 'the_content', function( $content ) {
    // iframe に loading="lazy" を追加(YouTube等の埋め込み)
    $content = preg_replace_callback(
        '/<iframe(?![^>]*loading=)([^>]*)>/i',
        function( $matches ) {
            return '<iframe loading="lazy"' . $matches[1] . '>';
        },
        $content
    );

    return $content;
} );
<!-- YouTube: ファサードパターン(軽量サムネイルで実際の動画を遅延読み込み) -->
<div class="youtube-facade" data-video-id="VIDEO_ID">
    <img src="https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg"
         alt="動画サムネイル" loading="lazy">
    <button class="play-button" aria-label="動画を再生">▶</button>
</div>
// YouTubeファサードのJS
document.querySelectorAll('.youtube-facade').forEach(function(facade) {
    facade.querySelector('.play-button').addEventListener('click', function() {
        const videoId = facade.dataset.videoId;
        const iframe  = document.createElement('iframe');
        iframe.src         = `https://www.youtube.com/embed/${videoId}?autoplay=1`;
        iframe.allow       = 'autoplay; encrypted-media';
        iframe.allowFullscreen = true;
        iframe.width       = '100%';
        iframe.height      = '100%';
        facade.replaceWith(iframe);
    });
});

ステップ5:CSSでフェードイン効果を追加する

/* 遅延読み込み画像のフェードイン */
img.lazy-img {
    opacity: 0;
    transition: opacity 0.4s ease;
}

img.lazy-img.loaded {
    opacity: 1;
}

/* 読み込み前のプレースホルダー(アスペクト比を維持してCLSを防ぐ) */
.image-placeholder {
    background: #f0f0f1;
    aspect-ratio: 16 / 9;    /* 画像のアスペクト比に合わせる */
    overflow: hidden;
}

.image-placeholder img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ブラー効果のプレースホルダー(LQIP: Low Quality Image Placeholder) */
.lqip-wrap {
    position: relative;
    overflow: hidden;
}

.lqip-wrap .placeholder {
    filter: blur(20px);
    transform: scale(1.05); /* ブラーの端を隠す */
    transition: opacity 0.3s;
}

.lqip-wrap .placeholder.loaded {
    opacity: 0;
}

注意事項

  • LCP(最大コンテンツ描画)に使われる画像にloading="lazy"を設定するとLCPスコアが悪化します。ファーストビューに表示されるアイキャッチ画像やヒーロー画像には必ずloading="eager"fetchpriority="high"を設定してください。
  • loading="lazy"はChrome/Firefox/Safari等の主要ブラウザで対応済みです。古いブラウザ向けのポリフィルが必要な場合はIntersection Observer APIを使ったJS実装を採用してください。
  • Intersection ObserverのrootMarginを大きくしすぎると遅延読み込みの効果が薄れます。200px程度が読み込みのガタつきを防ぎながら効果的な値です。

まとめ

遅延読み込みは「WordPress標準のloading="lazy"を確認→LCP画像はeager+fetchpriority=highに変更→コンテンツ内画像に一括でlazyを適用→iframeもloading="lazy"で遅延化→YouTubeファサードで動画埋め込みも最適化」の流れで整備します。関連記事:WordPress画像をWebPに変換して最適化する方法WordPressのCore Web Vitalsを改善してサイト速度を向上させる方法

お気軽にご相談ください

お見積りへ お問い合わせへ