Lazy Loading for Faster WordPress: Slow and Lazy Wins the Race

lazy dog lazy loading for wordpress

David wrote a really good article on WordPress site speed a few weeks ago. He also, casually, made WPShout a lot faster—up to an 89% desktop score on PageSpeed Insights, which is about as high as I’ve ever seen a WordPress site score.

Then we made the mistake of posting to the r/wordpress subreddit, where a member of Reddit’s large standing troll army dutifully informed us that the site “loads in over 2 seconds… brutally slow by today’s standard.”

In the resulting flame war, I realized that the Reddit troll actually had some good advice, including something we could do right away: lazy-load our assets, especially on the WPShout homepage, and especially the YouTube video embeds that come with our weekly Quick Guides. I resolved to try it and report back.

Introduction to Lazy Loading and the BJ Lazy Load Plugin

If we don’t load images and iframes for hidden parts of the page right away, then the part of the page that does load up right away will load much faster.

A webpage like the WPShout homepage is a giant bundle of stuff: text content, CSS stylesheets, JavaScript files, font assets—and, largest and slowest of all, image and iframe embeds. The default is that your browser fetches and renders all of this stuff at once, when you first request the page.

Lazy loading is based on a simple principle. Every image and iframe lives in a specific place in your page content. If that part of the page is hidden because you haven’t scrolled to it yet, then how about we don’t load the associated images and iframes yet? That way, the part of the page that does load up right away will load significantly faster, because it won’t be slowed down by the rendering of invisible images and iframes that the human user won’t be scrolling to for seconds or minutes anyway.

Then, the only thing we need to do is track the user’s scrolling behavior, and load in the page’s images and iframes one by one, right before she scrolls to them. As long as loading those items at the last second works properly, she’ll never know the difference, so we’ll do it later. We’re lazy.

Lazy loading can really “speed up WordPress”—meaning not the code itself or the backend, but your users’ experience of the front end, which is what matters—especially if your site relies heavily on images and iframe embeds. To get lazy loading onto a WordPress site requires a lot of PHP and JavaScript.

Our solution is not roll-your-own, which in this case would be crazy. Rather, I installed the outstanding (and free) BJ Lazy Load plugin onto WPShout. If you’ve already got another solution you like for lazy loading, go with that: most of the discussion here should apply to most implementations of lazy loading.

In this article, I’ll discuss the technical fundamentals of lazy loading, and then present and analyze the (spoiler: very significant!) impact it’s had on the performance of the WPShout homepage.

How it Works

How lazy loading works (at least through BJ Lazy Load specifically) is fairly straightforward relative to the coolness factor of what it’s doing—although, again, it’s plenty complex that I’m very grateful somebody else wrote it.

Like many things in WordPress, the plugin’s core functionality is spread across two languages: PHP and JavaScript.

BJ Lazy Load: The PHP

On the PHP side, the plugin replaces the page’s normal <img> and <iframe> tags with tags that look like the following. For an image:

<img width="870" height="400" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-type="image" data-lazy-src="https://iotvnaw69daj.i.optimole.com/w:auto/h:auto/q:mauto/f:avif/https://i1.wp.com/wpshout.com/wp-content/uploads/2017/09/three-pears.jpg?resize=870%2C400&ssl=1" class="lazy lazy-hidden attachment-featured-image-large size-featured-image-large wp-post-image" alt="" data-lazy-srcset="https://iotvnaw69daj.i.optimole.com/w:auto/h:auto/q:mauto/f:avif/https://i1.wp.com/wpshout.com/wp-content/uploads/2017/09/three-pears.jpg?resize=870%2C400&ssl=1 870w, https://iotvnaw69daj.i.optimole.com/w:auto/h:auto/q:mauto/f:avif/https://i1.wp.com/wpshout.com/wp-content/uploads/2017/09/three-pears.jpg?zoom=2&resize=870%2C400&ssl=1 1740w" data-lazy-sizes="(max-width: 870px) 100vw, 870px" />

For an iframe:

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"  class="lazy lazy-hidden" data-lazy-type="iframe" data-lazy-src="&lt;iframe class=&quot;ks_giveaway_iframe&quot; src=&quot;https://wpshout.com/giveaways/security/?embed_post=14055&quot; style=&quot;width:100%;height:880px;border:none;&quot;&gt;&lt;/iframe&gt;" alt="">

(If you’d like to know the physical PHP that does this, it’s the functions filter_images() and filter_iframes() in inc/class-bjll.php in the BJ Lazy Load plugin files.)

What are those <img> tags we just looked at? Well, they’re <img> tags that only reference a very, very small default “Loading…” placeholder .gif that itself takes almost no time to load and render.

Then they store the actual image URL (or, for an iframe, the entire HTML markup!) in their data-lazy-src attribute—or their data-lazy-srcset attribute for images that have responsive image sizes.

They also all have a shared class, lazy-hidden, which the plugin’s JavaScript is going to use to seek out, identify, and modify these elements as the user scrolls (see below).

In short, you don’t get images and iframes on page load, but rather pointers to images and iframes. This is much faster because your browser doesn’t have to fetch and render resources you’re not yet looking at.

BJ Lazy Load: The JavaScript

Okay, we’ve got a bunch of not-yet-fetched images and iframes. How do we turn them into the actual elements that users are expecting to see as they scroll the page?

Because it involves listening for and responding to in-browser behavior, that’s a job for JavaScript. The key function lives in js/bj-lazy-load.js (well, technically it’s js/bj-lazy-load.min.js that runs, but that’s impossible for humans to read), and it’s called show(). Here it is, with my comments added:

//show() takes one argument, an HTML element (one of the <img> tags with class 'lazy-hidden' that are taking the place of our actual images and iframes) 
show: function( el ) {
	// Remove 'lazy-hidden' class from element
	el.className = el.className.replace( /(?:^|\s)lazy-hidden(?!\S)/g , '' );
	el.addEventListener( 'load', function() {
		// Add 'lazy-loaded' class to element
		el.className += " lazy-loaded";
		// ?
		BJLL.customEvent( el, 'lazyloaded' );
	}, false );

	// Get type of element (image or iframe?)
	var type = el.getAttribute('data-lazy-type');

	// Images
	if ( 'image' == type ) {
		if ( null != el.getAttribute('data-lazy-srcset') ) {
			el.setAttribute( 'srcset', el.getAttribute('data-lazy-srcset') );
		}
		if ( null != el.getAttribute('data-lazy-sizes') ) {
			el.setAttribute( 'sizes', el.getAttribute('data-lazy-sizes') );
		}
		el.setAttribute( 'src', el.getAttribute('data-lazy-src') );
	} 
	// iFrames
	else if ( 'iframe' == type ) {
		// Get the <iframe> tag, saved as the 'data-lazy-src' property of an <img> tag
		var s = el.getAttribute('data-lazy-src'),
			// Make a <div>
			div = document.createElement('div');

		// Fill the  <div> with the <iframe> tag		
		div.innerHTML = s;
		// Get the <iframe> that results
		var iframe = div.firstChild;
		// Replace the original <img> tag with the <iframe>
		el.parentNode.replaceChild( iframe, el );
	}
}

Lazy loading won’t work at all without JavaScript, and the BJ Lazy Load authors were kind enough to wrap everything in noscript tags so that the few browsers with JavaScript disabled simply see no change—an example of the kind of thoughtfulness that makes a project like this take hundreds of hours to do properly despite a relatively simple central technology.

The Results: Lazy Loading and Site Speed on WPShout

Drum roll, please.

Google PageSpeed Insights

This initial PageSpeed Insights report on the WPShout homepage made me realize there was a problem. The homepage got a desktop/mobile score of 64%/56%—a big step back from David’s scores a few weeks ago.

Click to enlarge

Note the image files I’ve highlighted in orange: that’s something like 2 MB of giant, unoptimized images, coming across on every page load. Google wishes we’d compress them by 30% or so, which is fine, but why are we loading 2 MB of images from thumbs.videopress.com in the first place? More on that later.

With BJ Lazy Load enabled, our next score was massively improved, to 86%/69%:

bj lazy load pagespeed insights

Click to enlarge

Note, in particular, that all those odd, giant thumbs.videopress.com image files are gone. The remaining optimizable images are tiny in comparison.

Pingdom Results

One of the comments I got from Reddit is that PageSpeed Insights isn’t all that helpful or comprehensive, and that Pingdom is more helfpul overall, partly because it gives actual page load speeds. I decided to give Pingdom results as well. With BJ Lazy Load disabled, here’s what we got:

Pingdom before lazy load

Click to enlarge

In summary: 3 MB of assets to load, and a 3.1-second load time. That kind of sucks. With BJ Lazy Load enabled, we got:

Pingdom after lazy load

Click to enlarge

That’s 1.1 MB of assets to load (about 2/3 smaller), and a 2.3-second load time (about 1/4 faster). Not the fastest site ever made (right, Reddit?), but a large improvement for just activating a free plugin.

Comments on Results

PageSpeed Insights pointed me toward the primary thing that was ruining the homepage’s performance: one wonky video embed. That embed dropped something like 2 MB of images on the page load process, which aren’t used on-page and which look like this:

videopress video thumbs

Click to enlarge

No matter how you slice it, that’s really bad. And, uh, the embedding service (VideoPress) should feel bad.

bad feel bad futurama

Lazy loading would have helped our pagespeed no matter what, but on this particular week it meant the difference between “You can’t have the site until I chew through 2 MB of never-used video thumbnail image assets” and “Here you go!” That’s a big reason why the results were so dramatic.

What Can Lazy Loading Do for Your WordPress Site? (Or: Your Mileage May Vary)

In a lot of cases, lazy loading is an easy performance win for your WordPress site. However, as you dive into trying to speed up WordPress sites with BJ Lazy Load or a similar solution, here are a few things to remember about the results we got:

Lazy Loading is Most Effective for Long, Scrolling Pages

The page speed improvements we saw were for the WPShout homepage, which is a very long scroll of content. Loading that as the user scrolls makes a way bigger difference than it would on a shorter page. On a page where everything’s visible (a two-paragraph About page, say), lazy loading might have little or no effect.

Lazy Loading Spreads Asset Loading Out, But Doesn’t Make Assets More Efficient

Again, the main problem that lazy loading solved was the giant VideoPress embed. So it’s important to point out that lazy loading that extra 2 MB when you get halfway down the page doesn’t solve high data usage (hi and sorry, mobile users!) or other problems that come from just using resources inefficiently. It simply spreads the weight of those inefficiencies over the lifetime of a page load, giving the user a smoother experience overall.

So things like combining and minifying JavaScript and CSS files, image compression, and so on are still very relevant for your users’ experience. This isn’t a substitute for those sorts of best practices, but icing on top of them.

Lazy Loading Doesn’t Always Work

Lazy loading images can break on some sites. I tried BJ Lazy Load on a client site that loads six YouTube images on the homepage. It didn’t help the videos (because they’re loaded through a widget area, I’m pretty sure), but it did break the homepage slider that the client insists on having. Rats. A related plugin, Lazy Load for Videos, also doesn’t work, presumably also because it doesn’t look in widget areas.

The point is that lazy loading is plenty complex enough to not be “plug-and-play” on all sites—so make sure you click around once you’ve installed it.

Lazy Loading Can Have Side Effects

Lazy-loading is remarkably like laziness in the real world: it always has side effects, but sometimes you don’t notice the side effects if the lazy entity is really good at getting things done at the last minute.

Specifically, lazy loading can mean that, as you scroll a site, stuff isn’t loaded yet. The loading was too lazy, and that can lead to confused users. Here’s an example:

too lazy loading

“Too-lazy loading” is more likely if:

  1. The user is on a slow connection.
  2. The “lazy load threshold” is set to a low number.
  3. The page calls for huge assets, such as a 5MB image.

This “threshold” is a number of pixels away from the current viewable area. When an image or iframe is closer than that number to being viewable, that’s when the lazy-loading script kicks in.

WPShout’s threshold is set to 1,000 (I upped it from 500 after this article was initially published, as a result of seeing some flicker on an iPad), and things look good on a decent internet connection. If you set it to 20, like so,

then what you get is a website going “Sorry for the slow response. Here’s that image you requested. Apologies again, it’s been a super-busy week.” (Sound familiar?) What that looks like is big empty spaces on the page suddenly “filling in” with images that flicker to life as you scroll. It’s kind of cool, actually, but ultimately not the user experience you want.

To hedge against “too-lazy loading,” your best bets are, again, to set a high threshold, and to make sure that no one asset you serve is too giant, using the other tools (image compression, intelligent use of image sizes, and so on) available to you.

Last Thought: Use Pingdom Over (or In Addition To) PageSpeed Insights

One recommendation I’d like to make is to include a Pingdom test in addition to a PageSpeed Insights test as you’re figuring out your WordPress site performance. I’ve never used Pingdom before, but it does a lot of stuff PageSpeed Insights doesn’t, including present an actual number of seconds that the page took to load.

Other than that, I hope you’ve enjoyed this introduction to how to speed up WordPress for your users through lazy loading! We’d love to hear your thoughts below—whenever you get around to it.

Image credit: Ralph Arvesen


3 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
ZendBay
September 27, 2017 1:23 pm

I always have this belief that some users especially those who are not so tech savvy might easily think the website is not loading whereas it’s lazy load, especially the type that doesn’t load at all unless user scrolls down… But i have a clearer understanding with this your post,

Josh
September 19, 2017 4:16 pm

One caveat to mention: lazy loading doesn’t always play well with CDNs, depending on how each one works. If the assets are added to the page using JavaScript AFTER the initial server response, some CDNs may not pick up on the presence of those resources and may therefore fail to serve them.

That’s a bit of an outside case, but one I felt was worth mentioning within the overall lazy loading conversation.

Luke Cavanagh
September 19, 2017 3:43 pm

There are some beta lazy loading plugin options, so you can enable or disable by Post ID or CSS class.