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を改善してサイト速度を向上させる方法。