WP lazy loading using intercooler.js 03

Where I skip the server call.

A long trip around

The solution I’ve proposed in my previous article is far from being the best performing one.
Sure I’m still fiddling with lorempixel served images but, even swapping the code to use and return real WordPress stored images, the steps I’ve taken to use intercooler.js library to carry out lazy loading of responsive images is going in circles to get the job done; now the steps are:

  • when an element scrolls into view send a GET request to the /lazy-load-images route
  • along with that request is sent the width of the element containing the image
  • the server makes some math and returns the smallest possible image markup, in width terms, that can satisfy the image request
  • once the response gets back to the JavaScript part intercooler-js places the image markup and that’s shown on the page

The weak part, in respect to a standard JavaScript only solution, is the time taken to query the server and get the repsponse back from it.

What’s happening on the server?

Essentially the server is following this logic:

If requesting an image that will be shown inside a 100 pixels wide container then return an image that’s as close as possible to that width. E.g. If WordPress defines 150px, 300pxs, 600pxs and full width image sizes then return the 150px wide version.

The flow is simple enough not to need any contextual decision that only the sever could make and WordPress power is wasted here and the process could happen on the JavaScript side of things.
Luckily for me intercooler.js allows for some requests to be handled locally using the ic-ignore attribute and its prototyping capabilities.

Local sourcing

I will leverage intercooler.js local handling possibilities to avoid the long trip involving the server.
I’ve modified the markup for an image that’s to be lazily loaded to

<img class="lazy-img" ic-trigger-on="scrolled-into-view" data-srcset="2015-09-cat-3">

The image is now an img element proper and is not, as it was before, wrapped in a span element as before.
The image will still trigger an [intercooler.js](!g intercoolerjs) request when scrolled into view but the destination of that request will be now dynamically created.
I’ve modified the theme JavaScript file to set the ic-attr-src attribute on the img elements

(function ( $, undefined ) {
    'use strict';

    function closest_width( $elt ) {
        var width = $elt.width(), closest, w;
        for ( w in Lazy ) {
            var _w = Lazy[w], candidate = _w >= width ? _w : closest;
            if ( candidate != closest ) {
                closest = candidate;
                break;
            }
        }
        if ( !closest ) {
            closest = Lazy[w];
        }

        return closest;
    }

    $( document ).ready( function () {
        // make sure we have the Lazy dimensions object available
        if ( !Lazy ) {
            return;
        }

        $( 'img.lazy-img' ).each( function () {
            var $img = $( this ),
                srcset = $img.data( 'srcset' ),
                width = closest_width( $img );
            $img.attr( 'ic-attr-src', 'src:#' + srcset + '-' + width );
        } );
    } );

})( jQuery );

What the script is doing is to set the ic-attr-src attribute of each image taking the width the image container into account.
The available widths used to find the closest one are stored in an array, Lazy, localized by the theme functions.php file.

/**
 * Get image sizes with dimensions and crop value.
 *
 * From codex example.
 *
 * @param string $size
 *
 * @return array|bool
 */
function get_image_sizes_width() {
    global $_wp_additional_image_sizes;
    $sizes                        = array();
    $get_intermediate_image_sizes = get_intermediate_image_sizes();

    foreach ( $get_intermediate_image_sizes as $_size ) {

        if ( in_array( $_size, array( 'thumbnail', 'medium', 'large' ) ) ) {
            $sizes[ $_size ] = get_option( $_size . '_size_w' );
        } elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
            $sizes[ $_size ] = $_wp_additional_image_sizes[ $_size ]['width'];
        }
    }

    sort( array_unique( $sizes ) );

    return $sizes;
}


/**
 * Scripts
 */
add_action( 'wp_enqueue_scripts', function () {
    // run the `ic-src-attr` setting script before intercooler.js
    wp_dequeue_script( 'intercooler' );
    wp_enqueue_script( 'lazy-js', get_template_directory_uri() . '/js/lazy.js', [ 'jquery' ] );
    wp_enqueue_script( 'intercooler' );
    // localize image sizes smaller to larger
    wp_localize_script( 'lazy-js', 'Lazy', get_image_sizes_width() );
} );

Currently the Lazy array is just a dump of default WordPress image sizes width to the page so the available widths are:

  • 150px – thumbnail
  • 300px – medium
  • 1024px – large
  • n px – full; this could be anything

As an example the closest_width function will match an image titled flowers stored in the /wp-contents/2015/09 folder with an available width of 285px to the medium format resulting in an ic-attr-src=src:#2015-09-flowers-300 attribute.
The place inside the wp-content folder is taken into account to avoid naming collisions between images.
Since the source for the image src attribute is now pointing to an id on the page the functions.php file is taking care of printing such elements to the page.
The code is a stub that’s referring to an attachment ID defined on my local machine but it gets the idea across.

/**
 * @param $id
 *
 * @return mixed
 */
function get_img_name( $id ) {
    $matches = array();
    $url     = wp_get_attachment_url( $id );
    preg_match( "~/(\\d{4})/(\\d{2})/([^/]*)\\..*~u", $url, $matches );
    array_shift( $matches );
    $_name = implode( '-', $matches );

    return $_name;
}

/**
 * Footer src sets print
 */
add_action( 'wp_footer', function () {

    // stub code!
    $attachment_ids = array( 975 );

    $image_sizes    = get_image_sizes_width();
    ?>
    <div id="lazy-img-srcsets" class="ic-ignore" style="display: none;">
        <?php foreach ( $attachment_ids as $id ) : ?>
            <?php $_name = get_img_name( $id ); ?>
            <?php foreach ( $image_sizes as $size_name => $width ) : ?>
                <?php
                $id_attr = $_name . '-' . $width;
                $img_src = (array) ( wp_get_attachment_image_src( $id, $size_name ) );
                ?>
                <div id="<?php echo $id_attr ?>">
                    <?php echo reset( $img_src ); ?>
                </div>
            <?php endforeach; ?>
        <?php endforeach; ?>
    </div>
    <?php
} );

The printed HTML will result in

<div id="lazy-img-srcsets" class="ic-ignore" style="display: none;">
    <div id="2015-09-cat-3-150">
        http://wp.dev/wp-content/uploads/2015/09/cat-3-150x150.jpg
    </div>
    <div id="2015-09-cat-3-300">
        http://wp.dev/wp-content/uploads/2015/09/cat-3-300x225.jpg
    </div>
    <div id="2015-09-cat-3-1024">
        http://wp.dev/wp-content/uploads/2015/09/cat-3-1024x768.jpg
    </div>
</div>

Recap of the flow

After the page loaded the script will set the ic-attr-src on each image element, the image from the example above will have its markup set to

<img class="lazy-img" ic-trigger-on="scrolled-into-view" data-srcset="2015-09-cat-3" ic-attr-src="src:#2015-09-cat-3-1024" ic-src="#2015-09-cat-3-1024" ic-target="this.src" ic-id="1">

When scrolled into view [intercooler.js](!g intercoolerjs) will look for a #2015-09-cat-3-1024 element on the page and will find it between the elements printed by the functions.php file; the content of this element will be used to set the src attribute on the image.
The image will have its markup modified to

<img class="lazy-img" ic-trigger-on="scrolled-into-view" data-srcset="2015-09-cat-3" ic-attr-src="src:#2015-09-cat-3-1024" ic-src="#2015-09-cat-3-1024" ic-target="this.src" ic-id="1" src="http://wp.dev/wp-content/uploads/2015/09/cat-3-1024x768.jpg">

Since the image has now a real src attribute the request to the server for the image will start. The image has been lazily loaded.

Next

Removing the need to query the server to resolve requests locally has been a treasure find among [intercooler.js](!g intercooler.js) possibilities.
As per its application to add pain-free image lazy loading in WordPress I will iterate over a concept I now consider worth of prosecution to allow for the definition of specific image formats to use for the srcset and tap into WordPress filtering and dynamic ability to ge the job done.

I appreciate your input