WordPress sites loading slowly due to images? Native lazy loading with modern techniques dramatically improves performance.
// Add to functions.php
function add_lazy_loading_attributes($content) {
// Only run on frontend
if (is_admin() || wp_is_json_request()) {
return $content;
}
// Use DOMDocument for reliable parsing
if (class_exists('DOMDocument')) {
$dom = new DOMDocument();
@$dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'));
$images = $dom->getElementsByTagName('img');
foreach ($images as $image) {
// Skip if already has loading attribute
if ($image->hasAttribute('loading')) {
continue;
}
// Add loading='lazy' to all images
$image->setAttribute('loading', 'lazy');
// Add decoding='async' for better performance
$image->setAttribute('decoding', 'async');
// If image is above the fold (first 3 images), use eager
static $image_count = 0;
if ($image_count < 3) {
$image->setAttribute('loading', 'eager');
$image_count++;
}
}
// Save the modified HTML
$content = $dom->saveHTML();
}
return $content;
}
add_filter('the_content', 'add_lazy_loading_attributes', 99);
add_filter('post_thumbnail_html', 'add_lazy_loading_attributes', 99);
add_filter('get_avatar', 'add_lazy_loading_attributes', 99);
add_filter('woocommerce_product_get_image', 'add_lazy_loading_attributes', 99);
// Advanced: Native lazy loading with intersection observer fallback
function enqueue_lazy_loading_scripts() {
// Only for browsers that don't support native lazy loading
wp_add_inline_script('jquery', '
document.addEventListener("DOMContentLoaded", function() {
if ("loading" in HTMLImageElement.prototype) {
// Browser supports native lazy loading
console.log("Using native lazy loading");
} else {
// Fallback to intersection observer
console.log("Using intersection observer fallback");
const images = document.querySelectorAll("img[loading=\"lazy\"]");
if ("IntersectionObserver" in window) {
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
imageObserver.unobserve(img);
}
});
});
images.forEach((img) => {
// Store original src in data-src
img.dataset.src = img.src;
img.dataset.srcset = img.srcset || "";
img.src = "data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1 1\"%3E%3C/svg%3E";
img.srcset = "";
imageObserver.observe(img);
});
} else {
// Fallback for very old browsers
images.forEach((img) => {
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
});
}
}
});
');
}
add_action('wp_enqueue_scripts', 'enqueue_lazy_loading_scripts');
