Implement Lazy-Loading without Plugin

Lazy-load is a way to prevent images from loading before we scroll past it. There are many plugins that help you with this, some themes even have a built-in lazy-load function.

Here we will show you that it’s not hard to implement it yourself.

Step 1: Replace SRC Attribute

<!-- Convert this -->

<img src=".." srcset="...">

<!-- into -->

<img data-src="..." data-srcset="...">

When an image has src or srcset attribute, it will always load no matter what. So our first step is to temporarily prefix them with data-.

In WordPress, we can filter the content and replace them with regex:

add_filter( 'the_content', 'my_lazyload_content_images' );
add_filter( 'widget_text', 'my_lazyload_content_images' );
add_filter( 'wp_get_attachment_image_attributes', 'my_lazyload_attachments', 10, 2 );

// Replace the image attributes in Post/Page Content
function my_lazyload_content_images( $content ) {
  $content = preg_replace( '/(<img.+)(src)/Ui', '$1data-$2', $content );
  $content = preg_replace( '/(<img.+)(srcset)/Ui', '$1data-$2', $content );
  return $content;
}

// Replace the image attributes in Post Listing, Related Posts, etc.
function my_lazyload_attachments( $atts, $attachment ) {
  $atts['data-src'] = $atts['src'];
  unset( $atts['src'] );
  
  if( isset( $atts['srcset'] ) ) {
    $atts['data-srcset'] = $atts['srcset'];
    unset( $atts['srcset'] );
  }

  return $atts;
}

Step 2: Add Scroll Listener

When the image reaches your viewport, it should replace data-src into src, that way the image will start loading.

( function() { 'use strict';
  let images = document.querySelectorAll('img[data-src]');
              
  document.addEventListener('DOMContentLoaded', onReady);
  function onReady() {
    // Show above-the-fold images first
    showImagesOnView();

    // scroll listener
    window.addEventListener( 'scroll', showImagesOnView, false );
  }
  
  // Show the image if reached on viewport
  function showImagesOnView( e ) {
    
    for( let i of images ) {
      if( i.getAttribute('src') ) { continue; } // SKIP if already displayed
      
      // Compare the position of image and scroll
      let bounding = i.getBoundingClientRect();
      let isOnView = bounding.top >= 0 &&
      bounding.left >= 0 &&
      bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      bounding.right <= (window.innerWidth || document.documentElement.clientWidth);
      
      if( isOnView ) {
        i.setAttribute( 'src', i.dataset.src );
        if( i.getAttribute('data-srcset') ) {
          i.setAttribute( 'srcset', i.dataset.srcset );
        }
      }
    }
  }
              
})();

Don’t forget to enqueue the script:

add_action( 'wp_enqueue_scripts', 'my_lazyload_assets', 10 );
function my_lazyload_assets() {
  $js_dir = get_stylesheet_directory_uri() . '/js';
  wp_enqueue_script( 'my-lazyload', $js_dir . '/lazyload.js', [], '', true );
}

Step 3: Add Animation

You can customize the animation purely with CSS. For example, here’s a simple fade-in effect:

img[data-src] {
  opacity: 0;
  transition: opacity .25s ease-in-out;
  will-change: opacity;
}

/* appear animation */
img[data-src][src] {
  opacity: 1;
}

Feel free to change the transition timing to your taste and play around with translate.


Conclusion

Lazyload has become a necessity in all blogs and corporate websites. You don’t have to worry about SEO because Google themselves has confirmed that they can detect lazy-load.

There’s nothing wrong with using a plugin for this feature, but it’s good to know how it’s done. You can also customize it to your needs like customizing the animation and adding a delay.

If this code doesn’t work for you, please let us know in the comment below. It might clash with your themes or some plugins.

Default image
Henner Setyono
A web developer who mainly build custom theme for WordPress since 2013. Young enough to never had to deal with using Table as layout.
Leave a Reply

18 Comments

  1. Thank you for share this. I will use in my theme.

  2. Thank you so much for the great article and explanation. There are some pictures that are not using the lazy load method. That is most likely because of the theme but if you can provide some solution, that would be much appreciated.

    • Hi thanks for reading!

      Is the image part of main content or on Header / Footer?

      You need to add some filters depending on your theme. But you should be able to use the same regex to replace the src attribute.

      • Hi Henner, it is mainly for the pictures in the section that added by some additional features in the theme. If you check the page speed insight score, it shows which exact pictures are not getting the lazycode as how it supposed to be.

  3. Thank you for sharing this! I only have one issue with the code. Unfortunately, it also switches src to data-src in all emails, making email images impossible to render. Do you have any fix for that?

    • Hi Anna, You're welcome!

      Ah I was not aware that the code also affect email. What email plugin are you using?

      First, try checking which filter is applied to the email, is it the_content or wp_get_attachment_image_attributes?

      Maybe you can add a conditional check at the top of the function by checking what's inside global $pagenow

  4. Hi dear, Thanks a lot for sharing and great article! Lazyload is working perfect! But I have an issue.. the lazyload is not working if the image is coming from the Gutenberg Editor and it will wrap a "figure tag" to "img tag". Can we fix the issue?

    • Hi @Lyricsofindia,

      You're welcome!

      I just tested it again and it works on Gutenberg.

      Can you try checking whether the filter is applied? Try adding var_dump('anything'); in the function and see whether it's running.

  5. Thank you very much for the amazing tip! Some of our images on the main page are not being affected by the lazy load feature as they don't have data-src and data-srcset values... I believe that if we can add a data-src and data-srcset values for any image, the problem would be fixed. Would it be possible for you to help me to figure this out?

    • Hi Cornerstone College,

      You're welcome!

      Yes, unfortunately not every image will be affected because it depends on how the theme outputs the image.

      May I know which images are not affected? If it's on Header or Featured Image, then that's safe to not lazyload because they're at the top anyway.

      If its background-image then it's going to take a different approach.

  6. Thank you very much for this beautiful and simple article. I used it on my underscored theme and it worked like a charm. One question. What if I don't want to use a js file but add the script into the header instead? Thanks in advance.

    • Hi Sam,

      Glad you find it useful!

      It's better to put it on JS file so the browser can cache it. If you put the code directly under <script> tag, then your visitor will need to load that in every page he opens.

  7. Thank you for the nice and elegant script. Small question: shouldn’t the line
    `if( i.getAttribute(‘srcset’) ) {`
    be actually like this:
    `if( i.getAttribute(‘data-srcset’) ) {`

  8. Thank you Henner, for your blog and its useful content. Is there a way to exclude featured images from the lazy load effect?

    • Hi Alfredo,

      The featured image is usually printed with the filter wp_get_attachment_image_attributes, so you can add conditional on the function my_lazyload_attachments().

      I'm not sure how your theme does it, but you can check the class attribute. If it contains the class for featured image, prematurely return the $atts.

  9. Hi,

    thanks so much for the code, really helpful! However, the code in the functions.php lets thumbnails disappears in the media library in the WordPress admin. When I remove the code from the functions.php they reappear, so it's definitely root caused by it. Is there any workaround available?

    Thanks upfront! Martin

    • Hi Martin,

      I have just checked and my Media Library is fine (both in Media page and Media popup). Probably you have a plugin that alters Media Library to use wp_get_attachment_image_attributes.

      In that case, you can simply wrap that filter with if( !is_admin() ) { ... }.