Responsive images and lazy loading in WordPress

The technique responsive images prevents users from downloading unnecessary large images. Lazy loading only loads images if they are almost in the visible area, so the user does not download images he does not see. This post shows you how to use lazy loading with responsive images in WordPress.

Update from January 19, 2017: Since WordPress 4.4 you do not need the RICG Responsive Images plugin anymore — the responsive images feature was merged into the core with this release.

Lazy loading and responsive images for WordPress with »lazySizes«

For implementing responsive images into WordPress, there is the RICG Responsive Images plugin. Because the WordPress.org account is listed as one of the developers, the feature will probably be merged into the core (was merged into WordPress 4.4).

We use the script lazySizes for lazy loading the responsive images. To get this working, we have to modify the responsive images markup. The srcset and sizes attributes have to be changed into data-srcset and data-sizes. Besides that, we need to add the class lazyload and remove the src attribute.

To add a fallback for deactivated JavaScript, we generate a noscript element. Let us begin with the first code part:

Modifying the page and post content

<?php
defined( 'ABSPATH' ) or die( "Nothing to see!" );
/**
 * Plugin Name: Lazy Load Responsive Images
 */

function lazy_load_responsive_images ( $content ) {
   if ( empty( $content ) ) {
      return $content;
   }
   $dom = new DOMDocument();
   libxml_use_internal_errors( true );
   $dom->loadHTML( $content );
   libxml_clear_errors();
   foreach ( $dom->getElementsByTagName( 'img' ) as $img ) {
      if ( $img->hasAttribute( 'sizes' ) && $img->hasAttribute( 'srcset' ) ) {
         $sizes_attr = $img->getAttribute( 'sizes' );
         $srcset     = $img->getAttribute( 'srcset' );
         $img->setAttribute( 'data-sizes', $sizes_attr );
         $img->setAttribute( 'data-srcset', $srcset );
         $img->removeAttribute( 'sizes' );
         $img->removeAttribute( 'srcset' );
         $src = $img->getAttribute( 'src' );
         if ( ! $src ) {
            $src = $img->getAttribute( 'data-noscript' );
         }
      } else {
         $src = $img->getAttribute( 'src' );
         if ( ! $src ) {
            $src = $img->getAttribute( 'data-noscript' );
         }
         $img->setAttribute( 'data-src', $src );
      }
      $classes = $img->getAttribute( 'class' );
      $classes .= " lazyload";
      $img->setAttribute( 'class', $classes );
      $img->removeAttribute( 'src' );
      $noscript      = $dom->createElement( 'noscript' );
      $noscript_node = $img->parentNode->insertBefore( $noscript, $img );
      $noscript_img  = $dom->createElement( 'IMG' );
      $noscript_img->setAttribute( 'class', $classes );
      $new_img = $noscript_node->appendChild( $noscript_img );
      $new_img->setAttribute( 'src', $src );
      $content = $dom->saveHTML();
   }

   return $content;
}
add_filter( 'the_content', 'lazy_load_responsive_images', 20 );

Before displaying the post or page in the frontend, we filter its content with the function lazy_load_responsive_images. If the post content is empty, we just return $content without modification. To modify the image attributes to meet our needs, we use the PHP DOM classes (thanks for the hint to Franz Josef Kaiser).

At first, we create a new DOMDocument() object. With the method loadHTML(), we load the HTML code from the $content variable, which holds the content of the post. To avoid warnings with this method, which seems not to know HTML5 elements yet, the lines above and below the loadHTML() call are necessary.

Modification of images which were added with installed RICG Responsive Images plugin

Next, we loop through each img element. Because the responsive images plugin does not modify images which were added before the plugin activation, we have to check if the image is a responsive image. To do that, we check for the attributes sizes and srcset. If they are there, we get the content from both with the getAttribute() method.

After that, we need to set the new attributes data-sizes and data-srcset to the img element with the setAttribute() method. We use the respective variable with the result of the getAttribute() call as the second parameter. After doing this, we can remove the sizes and srcset attributes and save the image URL for the noscript image which will be added later. For that, we simply use the value of the src attribute. If no src attribute is given (for example this is the case for galleries), we use the data-noscript attribute.

Modify images without sizes and srcset attribute

Images which were added to posts or pages before installing the responsive images plugin neither have a sizes nor a srcset attribute. For these images, we fetch the URL like for the other images and add the data-src attribute.

Adding lazy load class and noscript element

Finally, we have to add a class to the images to get the lazy loading plugin working and create the noscript element. First, we save the existing classes of the image in the $classes variable and append the class lazyload in the next line. After that, we add the updated class string to the image again. Now we remove the src attribute (if it still exists).

Next, we create a new noscript element, which is inserted before the image. Besides that, we need an img element which gets the classes of the original image (I do not know why it has to be IMG here, but it does not work with img). After that, we append the image to the noscript element and add the src attribute.

Modifying image attributes of gallery images and post thumbnails

To modify the attributes of gallery images and post thumbnails, WordPress gives us the wp_get_attachment_image_attributes filter:

function lazy_load_responsive_images_modify_post_thumbnail_attr( $attr, $attachment, $size ) {
   if ( isset( $attr['sizes'] ) ) {
      $data_sizes = $attr['sizes'];
      unset( $attr['sizes'] );
      $attr['data-sizes'] = $data_sizes;
   }

   if ( isset( $attr['srcset'] ) ) {
      $data_srcset = $attr['srcset'];
      unset( $attr['srcset'] );
      $attr['data-srcset']   = $data_srcset;
      $attr['data-noscript'] = $attr['src'];
      unset( $attr['src'] );
   }

   $attr['class'] .= ' lazyload';

   return $attr;
}
add_filter( 'wp_get_attachment_image_attributes', 'lazy_load_responsive_images_modify_post_thumbnail_attr', 20, 3);

First, we check if the sizes attribute is set. If this is the case, we save its content to a variable, remove the attribute and add a data-sizes attribute. We do the same with the srcset attribute. Additionally, we add the data-noscript attribute and remove the src attribute.

Finally, we add the class lazyload to existing classes. Now only one thing is missing:

noscript element for post thumbnails

function lazy_load_responsive_images_filter_post_thumbnail_html( $html, $post_id, $post_thumbnail_id, $size, $attr ) {
   $dom = new DOMDocument();
   libxml_use_internal_errors( true );
   $dom->loadHTML( $html );
   libxml_clear_errors();
   foreach ( $dom->getElementsByTagName( 'img' ) as $img ) {
      $src     = $img->getAttribute( 'data-noscript' );
      $classes = $img->getAttribute( 'class' );
   }
   $noscript_element = "<noscript><img src='" . $src . "' class='" . $classes . "'></noscript>";
   $html .= $noscript_element;

   return $html;
}
add_filter( 'post_thumbnail_html', 'lazy_load_responsive_images_filter_post_thumbnail_html', 10, 5);

Parts of this code should look familiar to you: We get the classes and URL of the image and create the noscript element.

Adding the lazy load scripts

Let us insert the lazy loading script and a small style sheet to come to an end.

function lazy_load_responsive_images_script() {
   wp_enqueue_script( 'lazy_load_responsive_images_script-lazysizes', plugins_url() . '/lazy-load-responsive-images/js/lazysizes.js', '', false, false );
   wp_enqueue_style( 'lazy_load_responsive_images_style', plugins_url() . '/lazy-load-responsive-images/css/lazy_load_responsive_images.css' );
}
add_action( 'wp_enqueue_scripts', 'lazy_load_responsive_images_script', 20, 0 );

That is it. The style sheet just hides the original image, if JavaScript is deactivated. For that, a no-js class has to be given.

Tip: In the normal case, this should add a noscript element to all images. But if you are using a theme, which brings its own functions for displaying images, these will not get one. If you know a filter, which allows the modification of the HTML of all images, please leave a comment! 🙂

Marc uploaded the plugin to WordPress.org.

Related posts

Leave a Comment

Your email address will not be published. Required fields are marked *