2026年8月22日

2026年8月22日

WordPressで画像をLazy Loadして表示速度を改善する方法

はじめに

「画像の多いページがLCP(最大コンテンツ描画)の悪化で遅い」「スクロールしないと見えない画像まで最初に全部読み込んでいる」「Core Web VitalsのスコアでLCPとTBTが低い」——画像のLazy Loadで初期ページロードを大幅に削減できます。

症状・原因

WordPressはバージョン5.5からネイティブのloading="lazy"属性を自動付与していますが、LCPに影響するファーストビュー画像には付与すべきではありません。また背景画像やiframeは別途対応が必要です。

解決手順

ステップ1:ネイティブLazy Loadの動作を確認・調整する

// WordPressはデフォルトでloading="lazy"を付与
// 最初の画像(LCP候補)には付与しないよう制御する

// wp_lazy_loading_enabled フィルターで制御
add_filter( 'wp_lazy_loading_enabled', function(
    bool   $default,
    string $tag_name,
    string $context
): bool {
    // iframeには適用しない
    if ( 'iframe' === $tag_name ) {
        return false;
    }
    return $default;
}, 10, 3 );

// the_content の最初の画像には loading=eager を設定(LCP改善)
add_filter( 'the_content', function( string $content ): string {
    // 最初のimgタグだけ eager に変更
    $content = preg_replace_callback(
        '/<img([^>]+)loading=["\']lazy["\']([^>]*)>/i',
        function( array $matches ) use ( &$replaced ): string {
            if ( ! isset( $replaced ) ) {
                $replaced = true;
                return '<img' . $matches[1] . 'loading="eager"' . $matches[2] . '>';
            }
            return $matches[0];
        },
        $content
    );
    return $content;
} );

ステップ2:アイキャッチ画像にfetchpriority=highを設定する

// LCPのアイキャッチ画像に fetchpriority="high" を追加(WordPress 6.3+)
add_filter( 'wp_get_attachment_image_attributes', function(
    array   $attr,
    WP_Post $attachment,
    mixed   $size
): array {
    // 単一投稿ページのアイキャッチのみ対象
    if ( is_singular() && has_post_thumbnail() ) {
        $thumbnail_id = get_post_thumbnail_id();
        if ( $thumbnail_id === $attachment->ID ) {
            $attr['loading']       = 'eager';
            $attr['fetchpriority'] = 'high';
            $attr['decoding']      = 'sync';
        }
    }
    return $attr;
}, 10, 3 );

// wp_head でLCPリソースをpreloadする
add_action( 'wp_head', function(): void {
    if ( ! is_singular() || ! has_post_thumbnail() ) return;

    $thumbnail_url = get_the_post_thumbnail_url( null, 'large' );
    if ( ! $thumbnail_url ) return;

    printf(
        '<link rel="preload" as="image" href="%s" fetchpriority="high">%s',
        esc_url( $thumbnail_url ),
        "\n"
    );
}, 1 );

ステップ3:Intersection Observer APIで背景画像をLazy Loadする

// functions.php: Intersection Observer スクリプトをエンキュー
add_action( 'wp_enqueue_scripts', function(): void {
    wp_enqueue_script(
        'lazy-bg',
        get_theme_file_uri( 'assets/js/lazy-bg.js' ),
        [],
        '1.0.0',
        [ 'strategy' => 'defer' ]
    );
} );
// assets/js/lazy-bg.js
// Intersection Observer で .lazy-bg クラスの要素を監視
document.addEventListener('DOMContentLoaded', () => {
    const lazyBgs = document.querySelectorAll('[data-bg]');
    if (!lazyBgs.length) return;

    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                const el = entry.target;
                el.style.backgroundImage = `url(${el.dataset.bg})`;
                el.classList.add('bg-loaded');
                observer.unobserve(el);
            }
        });
    }, {
        rootMargin: '200px 0px', // 200px手前から読み込み開始
        threshold: 0
    });

    lazyBgs.forEach(el => observer.observe(el));
});
// テンプレートでの使い方
function render_lazy_bg_section( int $attachment_id, string $content ): void {
    $img_url = wp_get_attachment_image_url( $attachment_id, 'full' );
    printf(
        '<section class="hero lazy-bg" data-bg="%s">%s</section>',
        esc_url( $img_url ),
        wp_kses_post( $content )
    );
}

ステップ4:動画とiframeをLazy Loadする

// YouTube/Vimeo埋め込みを Facade パターンでLazy Load
add_filter( 'the_content', function( string $content ): string {
    // YouTube iframeをファサードに置き換え
    $content = preg_replace_callback(
        '/<iframe[^>]+src=["\']https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]+)[^"\']*["\'][^>]*><\/iframe>/i',
        function( array $matches ): string {
            $video_id  = $matches[1];
            $thumbnail = "https://img.youtube.com/vi/{$video_id}/maxresdefault.jpg";
            return sprintf(
                '<div class="yt-facade" data-videoid="%s" style="background-image:url(%s);aspect-ratio:16/9;cursor:pointer;background-size:cover">
                    <button class="yt-play-btn" aria-label="動画を再生">▶</button>
                </div>',
                esc_attr( $video_id ),
                esc_url( $thumbnail )
            );
        },
        $content
    );
    return $content;
} );
// YouTube Facadeクリックで本物のiframeを挿入
document.querySelectorAll('.yt-facade').forEach(facade => {
    facade.addEventListener('click', () => {
        const id = facade.dataset.videoid;
        const iframe = document.createElement('iframe');
        iframe.src = `https://www.youtube.com/embed/${id}?autoplay=1`;
        iframe.allow = 'autoplay; encrypted-media';
        iframe.allowFullscreen = true;
        iframe.style.cssText = 'width:100%;aspect-ratio:16/9;border:0';
        facade.replaceWith(iframe);
    });
});

ステップ5:Lazy Load効果をCore Web Vitalsで測定する

# WP-CLI で PageSpeed Insights を確認
# Chrome DevTools > Lighthouse タブで LCP・CLS を計測

# 画像の最適化状況を確認するbashスクリプト
find wp-content/uploads -name "*.jpg" -o -name "*.png" | while read f; do
    size=$(stat -c%s "$f")
    if [ $size -gt 204800 ]; then  # 200KB以上
        echo "Large: $f ($(( size / 1024 ))KB)"
    fi
done
// 管理画面に最適化されていない画像を表示
add_action( 'admin_notices', function(): void {
    if ( ! current_user_can( 'manage_options' ) ) return;
    $screen = get_current_screen();
    if ( $screen->id !== 'upload' ) return;

    $large_images = new WP_Query( [
        'post_type'      => 'attachment',
        'post_mime_type' => 'image',
        'posts_per_page' => 5,
        'meta_query'     => [
            [
                'key'     => '_wp_attachment_metadata',
                'compare' => 'EXISTS',
            ],
        ],
        'no_found_rows'  => true,
    ] );
    // 200KB以上の画像をカウントして表示...
} );

注意事項

  • ファーストビューのLCP画像にloading="lazy"を設定すると、LCPスコアが悪化します。アイキャッチや最初の画像にはloading="eager"fetchpriority="high"を設定してください。
  • Intersection ObserverはIE11で非対応です。必要な場合はpolyfillを使用するか、単純にloading="lazy"属性のみのフォールバックで十分です。
  • YouTube埋め込みをFacadeパターンにすると初期ロードが大幅に軽くなりますが、自動再生が期待できないためUX面での設計が必要です。

まとめ

Lazy Load実装は「WordPress標準のloading="lazy"を確認→ファーストビュー画像にloading="eager"+fetchpriority="high"+→背景画像はIntersection Observerでdata-bg属性から読み込み→YouTube/iframeはFacadeパターンで初期DOM軽量化→LighthouseでLCP・TBTを継続計測」の流れで整備します。関連記事:WordPressのWebP画像自動変換を実装する方法WordPressのCritical CSSを生成する方法

お気軽にご相談ください

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