Quick Guide: 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

    // 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('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.


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


  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.

  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.