Optimize Images on WordPress by Lazy Loading

Lazy loading images is an effective technique to improve the performance of your WordPress website. By loading images only when they enter the viewport, you can significantly reduce the initial load time and enhance the user experience. In this article, I’ll demonstrate how to implement lazy loading for images in WordPress using a custom approach.

1. Start the Buffer to Change <img> Tags

First, initiate an output buffer in the template_redirect action. However, ensure this does not occur during an AJAX call. Add a callback function to ob_start, which I will explain in the next section.

function youbou_start_buffer() {
	if ( wp_doing_ajax() ) {
        return;
    }

    ob_start( 'youbou_filter_buffer' );
}
add_action( 'template_redirect', 'youbou_start_buffer' );

2. Filter the Content

The callback function for ob_start will look for all <img> tags in the page’s HTML. It will replace the src and srcset attributes with data-src and data-srcset, respectively, preventing images from loading immediately. Additionally, it will add a class to the <img> tags for easier selection with JavaScript.

function youbou_filter_buffer( $buffer ) {

    $img_pattern = '/<img.*?src=\".*?\".*?>/';
    if ( preg_match_all( $img_pattern, $buffer, $matches, PREG_PATTERN_ORDER ) ) {

        foreach ( $matches[0] as $key => $image ) {

            $new_image = $image;
            //Change src to data attributes.
	        $new_image = preg_replace( '/<img(.*?)(src=)(.*?)>/i', '<img$1data-$2$3>', $new_image );
            //Change srcset to data attributes.
	        $new_image = preg_replace( '/<img(.*?)(srcset=)(.*?)>/i', '<img$1data-$2$3>', $new_image );
            //Add 'youbou-lazyload' class to each image that already has a class.
	        $new_image = preg_replace( '/<img(.*?)class=\"(.*?)\"(.*?)>/i', '<img$1class="$2 youbou-lazyload"$3>', $new_image );
            //Add 'youbou-lazyload' class to each image that doesn't already have a class.
	        $new_image = preg_replace( '/<img((.(?!class=))*)\/?>/i', '<img class="youbou-lazyload"$1>', $new_image );

            $buffer = str_replace( $image, $new_image, $buffer );
        }
    }

    return $buffer;
}

3. End the Buffer

Using the shutdown action, ensure that the output buffer is only flushed if it contains content and if the request is not an AJAX call.

function youbou_end_buffer() {
    if ( wp_doing_ajax() ) {
        return;
    }

    if ( ob_get_length() ) {
        ob_end_flush();
    }
}
add_action( 'shutdown', 'youbou_end_buffer', 999 );

4. Load Images with JavaScript

Use JavaScript to select all images with the youbou-lazyload class and observe them to detect when they enter the viewport. When an image is in the viewport, its data-src and data-srcset attributes are moved back to src and srcset, respectively. A class is added to the image to handle the opacity transition.

const imageLazyloading = () => {

    const images     = document.querySelectorAll( 'img.youbou-lazyload' );
    const options    = {
        rootMargin : '200px',
        threshold  : 0
    }
    const observer   = new IntersectionObserver( ( entries, observer ) => observerCallback( entries, observer ), options );

    /**
     * observe all the images
     */
    images.forEach( image => {
        observer.observe( image );
    });

    /**
     * Observer callback
     */
    const observerCallback = ( images, observer ) => {

        images.forEach( image => {

            // return if the image not in the dom yet.
            if( ! image.isIntersecting ) return;

            // change the data-src to src.
            loadImage( image.target );

            // remove the image from the observer.
            observer.unobserve( image.target );
        });
    }

    /**
     * Load an image
     */
    const loadImage = image => {

        // get the attributes.
        let src    = image.dataset.src;
        let srcSet = image.dataset.srcset;

        // set src attributes.
        if ( src ) {
            image.src = src;
            image.removeAttribute('data-src');
        }

        // set srcset attributes.
        if( srcSet ) {
            image.srcset = srcSet
            image.removeAttribute('data-srcset');
        }

        // add class for fading.
        image.classList.add('youbou-lazyload__loaded');
    }
};
document.addEventListener( 'DOMContentLoaded', imageLazyloading );

4. CSS for Fading

To ensure a smooth transition for the images as they load, use CSS to initially hide the images and then gradually make them visible.

img.youbou-lazyload {
    opacity: 0;
    transition: opacity 0.5s ease-in-out;
}

img.youbou-lazyload.youbou-lazyload__loaded {
    opacity: 1;
}

By following these steps, you can effectively implement lazy loading for images on your WordPress website, improving both performance and user experience.

#