WordPress lazy load images with blur up technique

Created on

18 Mar 2020

Updated on

20 Jan 2021

One of the most important things to note while developing a site is how fast it can load. With search engines giving higher priority to sites with lower loading time it is important to make your site as fast as possible.

Images have a good role in determining the load time of a website so most websites use lazy loading techniques to load the images only when it enters the user’s screen.

The “blur up” technique uses a very low-resolution image as a placeholder until the original image is loaded. This is a neat trick since the user gets a rough idea of the image and it also keeps the layout intact.

I’ve been using Gatsby a lot lately and there is a plugin that allows doing this. It was a neat feature and I wanted to implement it on a WordPress site that I was building.

So here’s how I did it without any WordPress plugins.

For lazy loading the images we will be using the JavaScript library lazyload.

First load the library by editing your footer file or functions.php file

If you are editing your footer file, then add the below code just before closing the body tag.

<script src="https://cdn.jsdelivr.net/npm/lazyload@2.0.0-rc.2/lazyload.js"></script>
<!-- https://github.com/tuupola/lazyload for additional configurations -->
<script>lazyload();</script>

If you are editing functions.php,

// Enqueue the script to footer

add_action('wp_enqueue_scripts','add_lazy_load_library');
function add_lazy_load_library(){
    wp_enqueue_script(
        'lazyload',
        'https://cdn.jsdelivr.net/npm/lazyload@2.0.0-rc.2/lazyload.js',
        array(),
        null,
        true // change to false if you need to load the script in head
    );
}
// Call the function in footer

function lazy_load_call() { ?>
    <script type="text/javascript">
        lazyload();
    </script><?php
}
add_action( 'wp_footer', 'lazy_load_call', 20 );

Calling lazyload() will load all the images with the class lazyload.

The next step is to create a low-resolution version of the images. This low-resolution image will act as a placeholder until the original image is loaded.

To do this, paste the below code into your themes functions.php

add_action( 'after_setup_theme', 'wp_blur_up_image_size' );
function wp_blur_up_image_size() {
    // Change 'tiny' to any unused name you like
    // 16px is the width/height of the new image however the image will keep its aspect ratio when cropping.
    // To disable keeping aspect ratio, change the fourth argument to true.
    add_image_size( 'tiny', 16, 16, false );
}

This will create a tiny, 16px wide version of each image that is uploaded from now on.

Note: This will not work for existing images, so you will have to regenerate the thumbnails of existing images. I used the plugin reGenerate Thumbnails Advanced, but you can use any method.

After the tiny image is generated, we need to extract the base64 encoded version of it and then add it to the database.

function filter_wp_generate_attachment_metadata( $metadata, $attachment_id ) {
    // Check if the 'tiny' version exists.
    if(isset($metadata["sizes"]["tiny"])){
        // Get the image src
        $src = wp_get_attachment_image_src($attachment_id, "tiny");
        // base64 encode the image
        $base64 = base64_encode(file_get_contents(wp_get_attachment_image_src($attachment_id, "tiny")[0]));
        // Add the base64 encoded value as a custom filed to the image
        update_post_meta( $attachment_id, 'base64', $base64 );
    }
    return $metadata; 
}; 

add_filter( 'wp_generate_attachment_metadata', 'filter_wp_generate_attachment_metadata', 10, 2 );

We need to handle two types of images, manually displayed images and images in posts.

Displaying the Image manually

When you are manually displaying an image (for example, when you show the featured image in the listing page), the base64 value should be given as the src attribute and the original URI should be given as data-src attribute.

We can make this process a function so that it will be easier to use.

function lazy_image($id, $alt=""){
    // Fetch the mime type and base64 value

    $mimeType = get_post_mime_type($id);
    $base64 = get_post_meta($id, "base64")[0];
    ob_start();
    ?>
    <img class="lazyload" src="data:image/<?= $mimeType ?>;base64, <?= $base64 ?>" data-src="<?= wp_get_attachment_image_src($id, "full")[0] ?>" alt="<?= $alt ?>">
    <?php
    echo ob_get_clean();
}

By adding the above code to the functions.php file, we can easily display a lazy loading image by calling lazy_image($id, $alt), where $id is the id of the image.

For example, if you are displaying the featured image of a post, you can display it as follows

<div class='featured-image'>
    <?= lazy_image(get_post_thumbnail_id(),"Image description"); ?>
</div>

Displaying Images in posts

For displaying the images in posts, we will have to replace all the images in the post content with the custom lazy loading image.

The the_content filter is used to replace the images before the content is displayed.

The below function will search the content for images, find its attributes and then replace it with a lazy loading one. Paste it into your functions.php file.

add_filter( 'the_content', 'filter_the_content_in_the_main_loop' );
 
function filter_the_content_in_the_main_loop( $content ) {
     // Check if we are in the main query and in a single page
     if ( is_single() && in_the_loop() && is_main_query() ) {

        // Find all figure elements from the post content

        $pattern = '#<\s*?figure\b[^>]*>(.*?)</figure\b[^>]*>#s';

        if(preg_match_all($pattern, $content, $matches)){
            foreach($matches[0] as $index => $match){
                // Convert the string to DOM objects so that we can access element attributes easily
                $dom = new DOMDocument();
                @$dom->loadHTML($match);

                // The figure element
                $figure = $dom->getElementsByTagName("figure")[0];
                $figureClass = $figure->attributes["class"]->value;


                // The below lines are used to extrace the size of the image.
                // The size of the image is added to the class in the form size-{size}
                $end = strpos($figureClass, " ", strpos($figureClass, "size-"));
                $end = $end ? $end : strlen($figureClass);

                $imgSize = substr($figureClass, strpos($figureClass, "size-")+5, $end);

                // Check if image was resized
                $isResized = strpos($figureClass, "is-resized");

                // The image element
                $img = $dom->getElementsByTagName("img")[0];
                $imgAttributes = $img->attributes;

                // Store all attributes of the image to an array
                $newImageProperties = array();
                foreach($imgAttributes as $attr){
                    $newImageProperties[$attr->name] = $attr->value;
                }

                // The below two lines are used to extract the class of the image. The class contains the id of the image.
                preg_match('/\bwp-image-\w+/',$newImageProperties["class"], $idMatch);
                $imageId = substr($idMatch[0], strlen("wp-image-"));
                
                // Get the base64 and mimetype of the image
                $base64 = get_post_meta($imageId, "base64")[0];
                $mimeType = get_post_mime_type($imageId);

                $image = wp_get_attachment_image_src($imageId, $imgSize);

                // These properties have to be changed since they need to be handled by lazyload
                $newImageProperties["class"] = $newImageProperties["class"]." lazyload";
                $newImageProperties["data-src"] = $newImageProperties["src"];
                $newImageProperties["src"] = "data:image/$mimeType;base64, $base64";
                $newImageProperties["data-srcset"] = $newImageProperties["srcset"];
                $newImageProperties["srcset"] = "";

                // If the image is not resized, set the image width as width attribute
                if(!$isResized){
                    $newImageProperties["width"] = $image[1];
                }

                // Create a new image with all the original attributes

                $newImage = "<img";
                foreach($newImageProperties as $prop => $propValue){
                    $newImage.=" $prop='$propValue'";
                }
                $newImage.= "/>";

                // Replace the old image with the new one.
                $content = str_replace($matches[1][$index], $newImage, $content);
            }
        }
    }
 
    return $content;
}

Now if you go to your site, you will see that your images will have a blurred up effect before loading.

Live demo.

If you can’t see the blur up effect because of faster internet ⚡, open the dev tools (F12), go to the “Network” tab and change the “Online” dropdown to Fast/ Slow 3G, and then reload the site.

Have suggestions/improvements or doubts? Comment below.

Comments

Post a comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.