Build Your Own Simple Lazy Loading Functionality in WordPress

Originally posted on -

When you’re looking to incorporate any given feature into your WordPress application, there’s rarely a shortage of third-party plugins out there to make it happen. But sometimes, whether you’re trying to avoid the inevitable complexity an unfamiliar third-party plugin introduces, or for some other reason, you might feel called to take a stab at developing that feature on your own. Lazy loading images could be one of these features, and fortunately, it’s fairly simple to set it up yourself and start reaping the performance benefits.

I’m assuming here that you have full development dominion over your WordPress application, and that you’re relatively familiar with the WordPress Plugin API – the characteristic hook system that makes WordPress development as flexible as it is. While we could set up lazy loading without a plugin by putting everything inside your theme’s functions.php file, we’ll be rolling our own extremely basic plugin. This is generally a good idea over just using your theme – it’ll separate concerns a little better, you’ll be able to switch themes without losing functionality, and it’ll be easy to toggle whenever like, which is particularly helpful when debugging.

Know this: I won’t be going in-depth on the philosophy of what makes a well-crafted WordPress plugin. Experiment and argue about all that jazz on your own. I’m just gonna give you the bare bones of what it takes to make a lazy loading plugin with just a few lines of code. Let’s get started.

Set Up Your Plugin

In your plugins directory, create a simple-lazy-loading directory and a file inside it named simple-lazy-loading.php. Open that file, and place the following at the top:

<?php

/** Plugin Name: Simple Lazy Loading*/

At bare minimum, you now have a plugin that can be activated in the the WordPress admin. Head there and switch it on.

After you’ve done that, open up the file you just created and we’re ready to go!

Let’s Get Lazy

You can lazy load a lot of things, but here, we’re just focusing on lazy loading images stored in your WordPress database that are spit out as post or page content. Basically, images you upload and use via the WordPress admin.

To do this, let’s use Lozad for our lazy loading JavaScript library. It has no jQuery dependency, appears to be pretty actively maintained, and it leverages the Intersection Observer API, which will get you better performance, especially as browser support grows in the future. In the meantime, there’s a polyfill you can include.

Getting Lozad Up on Its Feet

First, enqueue the Lozad library. We’ll also want to throw in the official W3C polyfill for the Intersection Observer API. We’ll enqueue both of these in the footer because we’re responsible web developers and don’t want these scripts to block page rendering.

add_action('wp_enqueue_scripts', function () {
   wp_enqueue_script( 'intersection-observer-polyfill', 'path-to-intersection-observer.js', [], null, true );
   wp_enqueue_script( 'lozad', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/lozad.min.js', ['intersection-observer-polyfill'], null, true );
});

Next, initialize Lozad. Out of the box, a “lozad” class is used to watch for elements to be lazy loaded, but since we’re going to modify the configuration a bit anyway, let’s change that to a more generic “lazy-load” class. Also, note that I’m just using wp_add_inline_script here, since the amount of JS we’re writing is small. You’re welcome to put that in a separate JS file – just make sure it executes after lozad is loaded.

add_action('wp_enqueue_scripts', function () {
    wp_enqueue_script( 'intersection-observer-polyfill', 'path-to-intersection-observer.js', [], null, true );
    wp_enqueue_script( 'lozad', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/lozad.min.js', ['intersection-observer-polyfill'], null, true );
    wp_add_inline_script( 'lozad', '
        lozad(".lazy-load").observe();
    ');
});

Neat! But what if I don’t want to target elements by class? No problem! Instead of defining a class to be watched, you can pass in a NodeList. In that case, our wp_add_inline_script section would look something more like this:

wp_add_inline_script( 'lozad', '
	var myElements = document.querySelectorAll("#main img");
	lozad(myElements).observe();
');

If we set things up this way, Lozad will watch for any images that are children to #main.

Our next step is to add our target class to every image we want to lazy load, as well as change the src attribute to data-src****. This will prevent the browser from loading the images by default (which would block the rest of the page from rendering), and only do so when lozad changes data-src back to src, which will happen when users scroll to those images.

For any images on custom pages outside of the WordPress database, it’s pretty easy to set up the attributes we need. Just manually do it. Unfortunately, working with the images that are stored in the database will require that we filter everything via the_content hook. I don’t love the idea of having to filter post content like this, but if you’re properly caching your site like you should be, this is less of an issue, at least in terms of performance.

add_filter('the_content', function ($content) {
	//-- Change src to data attributes.
	$content = preg_replace("/<img(.*?)(src=)(.*?)>/i", '<img$1data-$2$3>', $content);

    	//-- Change srcset to data attributes.
  $content = preg_replace("/<img(.*?)(srcset=)(.*?)>/i", '<img$1data-$2$3>', $content);

	//-- Add .lazy-load class to each image that already has a class.
	$content = preg_replace('/<img(.*?)class=\"(.*?)\"(.*?)>/i', '<img$1class="$2 lazy-load"$3>', $content);

	//-- Add .lazy-load class to each image that doesn't already have a class.
	$content = preg_replace('/<img((.(?!class=))*)\/?>/i', '<img class="lazy-load"$1>', $content);

	return $content;
});

We’re doing three separate things with our filter here.

Technically, you’re done. Now, each page on which you have content populated from the WordPress editor, images should not load by default, and instead load only when they come into view. But that might not be good enough.

Let’s Make It All Prettier

If images only load when they come into view, you’ll see an ugly pop on the page when that happens. To make everything happen in a prettier fashion, we have some options at our disposal.

Let’s adjust the rootMargin so images start loading when they’re about to come into view. This way, they can be ready to go before the user even gets there, and everything will appear much more seamless. Go back to our wp_add_inline_script call:

wp_add_inline_script( 'lozad', '
	lozad(".lazy-load", {
		rootMargin: "300px 0px"
	}).observe();
');

Now, when images come within 300px of being visible, Lozad will trigger them to start loading. Feel free to adjust that value as you see fit.

We can also add a class after our images load, so they fade in a super pretty, majestic way. Do that by adding a loaded callback:

wp_add_inline_script( 'lozad', '
	lozad(".lazy-load", {
		rootMargin: "300px 0px",
		loaded: function (el) {
			el.classList.add("is-loaded");
		}
	}).observe();
');

And add some CSS that’ll hide the images until they’re fully loaded, and then allow them to fade in. Note that if you do add the following CSS, you’ll need to enqueue it in a stylesheet or by some other means.

.lazy-load {
    transition: opacity .15s;
    opacity: 0;
}

.lazy-load.is-loaded {
    opacity: 1;
}

Neat! Now, every image will now have an opacity of 0, until they’re loaded in, when is-loaded will fade them in.

Soon, This May All Be Meaningless

Lazy loading is a good, responsible thing to implement, but, all of this will likely be possible by default in browsers. In fact, Chromium-based browsers and Firefox already do have a native API for it. So, pay attention. In a short while, all of what I’ve just showed you might be pointless, and I will need to put a sad disclaimer at the top of this post that you might just be wasting your time by reading this.

Hope this helps!


Was this post helpful? Thanks for the feedback!

Get an email whenever I publish a new post or project.

I won't send you spam. Unsubscribe whenever.
my face

Alex MacArthur is a software developer working for Dave Ramsey in Nashville, TN. Soli Deo gloria.

Leave a Comment

8 comments
  • Rafa Rahmandika
    That filter function what I'm looking for all the time. Great stuff alex!
  • Anna Grunduls

    Thank you for this!
    I'm wondering if it's possible to disable this in emails? I noticed that my WooCommerce emails stopped displaying images when I implemented this.

  • Zachary Miller
    I launched my new website: https://superfeincreative.com. I created my own Lazy Load plugin from this tutorial, but for some reason on initial page load the images all come in awkwardly and in a jolting and non-smooth way. But when you refresh the page and scroll the images fade in gracefully. Any idea why on initial paint the images aren't being properly lazy loaded? Could it be js priority or something? Would appreciate any guidance here. Does anyone else have this issue?
  • Zachary Miller
    Brilliant article Alex! I had a question: the preg_replace is specifically working to add/replace, but only on img tags added via the WYSIWYG editor, correct? The function seems to be targeting the_content(). Anyway what I'm wondering is, how would you tweak the preg_replace function so that it would be applied to ALL images, even images I had coded manually in the HTML, or added via a loop which I had also coded manually. Seems this lazy loading plugin is really only useful if all the content on the website is generated via the editor.
    4 replies
    • Zachary Miller
      Hi Alex, this is sort of for everyone, but also to you Alex in case you have any important comments on this. I couldn't get the .lazy-load transition effect to take effect on certain images. So I applied the .lazy-load class to the parent div of the img tag and that worked great. As you scroll down the page the images fade in gracefully. Hope this helps someone else, and is also correctly applied. If you have anything to say about it Alex I would be happy to hear from you on this.
      • Alex MacArthur
        Awesome, glad this stuff is helpful! Yes, you should be adding data-src do your images. Otherwise, those images are likely just loading like normal, and you're not benefiting from lazy loading.

        As for lazy loading images generated via the_post_thumbnail, you could probably use the post_thumbnail_html filter to apply the same regex patterns I use here to those image tags. That should get you the same result.

        https://developer.wordpress...

        There's likely an ACF filter you could use to do the same thing, but I haven't dug into that at all.

        • Alex MacArthur
          Hi, Zachary -- Yes, this mainly deals with images placed on the page via the editor (like blog post images, etc.). I do include a note in there about lazy loading other images too, such as those at are hard-coded. You'd just need to add the 'lazy-load' class and user 'data-src' instead of 'src.' There's some manual work for these cases, but the result is the same, as long as the correct JavaScript is enqueued on the page.
          • Zachary Miller

            Thanks Alex! Yes it works great when I add the class manually to hard-coded img tags. It seemed to work without data-src actually as I didn't add that manually. But perhaps I should be? Manually changing the src to data-src can be difficult, especially with the_post_thumbnail when I'm looping through a custom post type. Not really sure how I would go about that. And what about images output via ACF? I really love the idea of building my own lazy load plugin, I love being plugin independent, but the reality of actually creating my own that covers every use case is an enormous challenge. Your article certainly got me started, so thanks for that. :)
            P.S. Here's a cool background img lazy load CodePen: https://codepen.io/malchata...

          • Zora Creative
            I'm doing the same thing but using lozad. I used the code on images above but it took away the alignment of the images set by Wordpress WYSIWYG editor which I liked. Can you modify the regular expression to not strip away a class that is currently assigned to the image?
            1 reply
            • Alex MacArthur
              @zoracreative Thanks for letting me know about this! I've updated that regex and it appears to be working more reliably for me now -- no more wiping out existing classes.
            • Hubert Nguyen (Ubergizmo)
              Alex, thanks for the interesting post. At the moment, the sample code removes existing classes if there's more than one - I know it's supposed to work though because the regular expression seems correct. It is common for images to have multiple classes such as : aligncenter size-full wp-image-298205. Maybe consider to include this option in the code above. Cheers.
              1 reply
              • Alex MacArthur
                @hubertnguyen Sorry for the delay, and thanks for letting me know about that! I've updated the regex patterns, and that issue appears to be resolved now. No more wiping out existing classes.
              • Zora Creative
                How would you add to the regular expression so that it includes the no javascript scenario. This is also good for Google SEO if it doesn't run the javascript.
                4 replies
                • Alex MacArthur
                  Great point -- You're right, the best way to do that would probably be to follow each image tag with a version wrapped in <noscript> tags. I may work that into the article when I find time.
                  • Zora Creative
                    This is seen here: <noscript><img src="image.png"/></noscript>
                    • TheAlfrex

                      I totally agree with Zora. You can find more information in this video (minute 33).
                      https://www.youtube.com/wat...
                      Google explains the importance of noscript.
                      I hope you can add that tag soon to this post =D

                      • TheAlfrex

                        I added the no-script tag, but I can't add the alt attribute to the no-script tag. According to Google newest article about lazy loading it is important to have it.
                        Hi Alex, would you mind adding the code with the noscript & alt attribute?
                        I already tried so much but I cannot. Help :D

                        add_filter('the_content', function ($content) {

                        //-- Change src to data attributes.
                        $content = preg_replace('/<img(.*?)(src="[^"]*")([^>]*?)>/i', '<img$1data-$2$3> <noscript><img $2=""></noscript>', $content);

                        //-- Change srcset to data attributes.
                        $content = preg_replace("/<img(.*?)(srcset=)(.*?)>/i", '<img$1data-$2$3>', $content);

                        //-- Add .lazy-load class to each image that already has a class.
                        $content = preg_replace('/<img(.*?)class=\"(.*?)\"(.*?)>/i', '<img$1class="$2 lazy-load"$3="">', $content);

                        //-- Add .lazy-load class to each image that doesn't already have a class.
                        $content = preg_replace('/<img((?:(?!class=).)*?)>/i', '<img class="lazy-load" $1="">', $content);
                        return $content;
                        }, 12);

                      • Roberto
                        I really liked it, is there any way to make it work for background images? Is it possible to add a loading icon or something like that so people know something is loading?
                        1 reply
                        • Alex MacArthur
                          Yes, Lozad does support background images! Just apply a data-background-image="/image.jpg" attribute with your image source along with the "lazy-load" class, and that should do the trick.

                          I haven't dug into Lozad-specific loading icon functionality, but I've seen people do something like this, which might be good enough:

                          <img src="loading.gif" data-src="lazy-loaded-image.jpg">