Manual Responsive Lazy Loading Images Conditionally

We all want to lazy load images below the fold for best performance. But what if an image is ABOVE the fold on desktop, but BELOW the fold on mobile?

The Issue

I ran into this situation recently with images in a sidebar. The sidebar has, as many blogs do, a little bio box with an photo of the blogger. The sidebar even has a CSS background image, too.

The sidebar and profile image (and a background image!) are above the fold on desktop, but on mobile, the sidebar is stacked far below the post content out of initial view.

A post with a sidebar on the right containing images above the fold
A post with the sidebar not in the viewport on mobile

If I lazy load the image, that will benefit mobile, but not desktop. If I exclude the image from lazy load, it will benefit desktop but not mobile, because it will be excluded regardless of mobile or desktop (with the image tool Optimole that I am using, that is).

Furthermore, the lazy loading technique may be a little different between the regular image and the CSS background image.

The Solution

If you can afford it, you may be able to use some tools that detect images below the viewport on the fly, rather than relying only on exclusions by things like filename or class.

Update July 2022: Litespeed Cache 5.0 comes with the Viewport Images (VPI) feature, which I believe addresses this issue.

But I think many of us, including myself, are using image optimization plugins such as Optimole, which don’t let you exclude an image based on viewport width (at least not as of this writing).

So we can turn to just a few lines of JavaScript to get the job done.

As we have two images we want to adjust, let’s start with the regular (non-background) image.

The Regular Image

We begin by ensuring lazy load is enabled for our images in the plugin settings or whatever tool we are using. We are already done for mobile.

Now to cancel the lazy load for desktop/larger viewports. Here are a couple things you can try. One of them should be enough.

Method 1: Dynamically Remove loading=”lazy” Attribute

Perhaps the most straightforward approach is to simply remove the loading="lazy" attribute on the image element.

You will just need to change the class name below from “.regular-img” to another class that your image has, or else find another way to target this image element. If you are using the block editor in WordPress for example, you can add classes to any block in the Advanced field.

const regImg = document.querySelector(".regular-img");
if (regImg !== null) { regImg.removeAttribute("loading"); }

This assumes your lazy loading method is making use of the “loading” attribute, otherwise you may need to put a different attribute.

Method 2: Dynamically Add an Excluded Class

We will need to specify a class name for which lazy loading will be excluded. I believe many image optimization plugins will have a field for this. Let’s call it “no-lazy” and enter it into the exclusion settings.

Now, let’s write some JavaScript which adds this “no-lazy” exclusion class to our image. Again, just change the class name below from “.regular-img” to another class that your image has, or else find another way to target this image element.

const regImg = document.querySelector(".regular-img");
if (regImg !== null) { regImg.classList.add("no-lazy"); }

This didn’t actually work for me sometimes…it might be related to the fact that my attempt is happening too late after the exclusions and lazy load scripting have finished processing.

Method 3: Using the Loading = “eager” Attribute

If using WordPress’s default lazy loading, which utilizes a loading = "lazy" attribute on the image tags, you can have it bypass the lazy load by utilizing a loading = "eager" attribute instead.

const regImg = document.querySelector(".regular-img");
if (regImg !== null) { regImg.setAttribute("loading", "eager"); }

However, since we would be changing this attribute dynamically, you would need to test to verify that dynamically changing this attribute has the intended effect.

Method 4: Real-time Find and Replace

I have not tested this approach, but you could try a plugin such as Real-time Find and Replace to edit the HTML output of your image tag in whatever way you want. The HTML of your page is supposed to be finished being tweaked before it is handed to the browser.

So you could add classes and remove attributes without the need for JavaScript, potentially allowing the image to be recognized as non-lazy and load sooner than Methods 1-3 above.

But again, you would only want this to execute on certain conditions. You would need a way to disable this plugin on mobile, which you could do with the Freesoul Deactivate plugin. However, Freesoul Deactivate warns: “Be sure you have a server cache plugin that distinguishes between mobile and desktop.

Viewport Width Condition

Great, now of course we only want this JavaScript code to run when the image is in the initial viewport on larger screen sizes. Find out at which viewport width or breakpoint that the sidebar or image is above the fold in the initial view (use Inspect Element/DevTools to help), and use that in the code below. In this example, the theme’s sidebar is side-by-side at around 768px, and if it’s smaller then it’s stacked below the post content.

if (window.matchMedia("(min-width: 768px)").matches) { 
  const regImg = document.querySelector(".regular-img");
  if (regImg !== null) { regImg.removeAttribute("loading"); }
}

So we wrapped our code an ‘if’ statement such that it only runs when the viewport screen width is 768px or higher, at which point the “no-lazy” class is added to the image and the lazy loading plugin sees that and excludes it.

The Background Image

Background images might be a little tricky, but in my case it was very simple.

The plugin Optimole I was using does allow background images to be lazy loaded by designating a class name and assigning that to the background image elements. But maybe not all lazy load plugins can handle background images, but for this post let’s assume yours has that functionality.

First of all, if you’re using an image optimization plugin to lazy load background images, you might need to ensure the CSS of the background image (the image’s source URL specifically) can be found within inline CSS in the HTML, rather than in an external CSS file.

Now if you manage to lazy load the background image, you should be able to inspect it and in my case, I noticed Optimole was adding the class “optml-bg-lazyloaded” and CSS to the background image element:

html .bg-img:not(.optml-bg-lazyloaded) {
    background-image: none !important;
}

I guess the plugin does this to make sure the background image doesn’t load initially, and the plugin’s JavaScript adds the “.optml-bg-lazyloaded” class when it detects the image is ready to display in the viewport.

So all I had to do was add that class on larger viewports to stop the lazy load for that image. So I can use the same JavaScript condition as before with the regular image. Here is the code that combines both of them.

if (window.matchMedia("(min-width: 768px)").matches) { 
  const regImg = document.querySelector(".regular-img");
  if (regImg !== null) { regImg.classList.add("no-lazy"); }
  const bgImg = document.querySelector(".bg-img");
  if (bgImg !== null) { bgImg.classList.add("optml-bg-lazyloaded");
}

Inserting the Solution

We can add the JavaScript to the site using a code snippets plugin, or just by using the functions.php of a child theme. But you don’t want to insert the script too early before the element even exists.

And since it’s just a few lines of code, which can make the JavaScript inline. For images in the <body> tag, we may need to insert our code into the footer to ensure the <body> has been parsed. To insert an inline script into the footer, you can do something like this:

add_action( 'wp_footer', 'add_optimized_inline_script', 0 );
function add_optimized_inline_script() {
	?><script>
// insert JS here
	</script><?php
}

If you want to take the JavaScript optimization a step further, you can wrap it in an IIFE and minify the script before pasting it into your site, but again, it’s just a few lines of code.

Caveat

One thing to note here is that this approach, in particular Methods 1 & 2, due to timing, might not be the same as if the image was truly excluded from the image optimization plugin’s settings from the beginning.

These solutions might be thought of more along the lines of allowing the image to load when they are recognized as not being lazy loaded as late as the footer of our page. But honestly I’m not really sure how much of a difference this makes.

One thing I know is when I face a situation where lazy loaded images should not be lazy loaded on desktop, I can quickly try these solutions to see if this helps.

In any case, we can rest knowing that mobile, the “weaker” device, and I would also say the more commonly used device, is fully optimized in this regard.

TL; DR

We have some images that are in the initial viewport on desktop but not on mobile, and we want to lazy load only what is below the fold on initial view for each device.

We optimize mobile by turning on lazy loading images in general.

Then we turn to JavaScript to “break” the lazy load when we recognize we are on a large enough viewport. Alternatively, we could attempt the Real-time Find and Replace plugin to manipulate the image tag’s HTML earlier on without JavaScript, and disable the Real-time Find and Replace plugin on mobile by using the Freesoul Deactivate plugin (but this might get tricky with separate mobile-only caching).

Then we add the JavaScript to the footer assuming our images are in the <body> which needs to be parsed first.

add_action( 'wp_footer', 'add_optimized_inline_script', 0 );
function add_optimized_inline_script() {
	?><script>
            if (window.matchMedia("(min-width: 768px)").matches) { 
              const regImg = document.querySelector(".regular-img");
              if (regImg !== null) { regImg.classList.add("no-lazy"); }
              const bgImg = document.querySelector(".bg-img");
              if (bgImg !== null) { bgImg.classList.add("optml-bg-lazyloaded");
            }
	</script><?php
}

Check to see if your work has made any improvement.

Leave a Comment