Update: prImages has been superseded by Prime

In my quest to optimize websites for loading performance, I was intrigued by loading images progressively. Progressive image loading is a technique that basically loads a low quality version of an image first and loads a better version over time. (This differs from formats that are progressive on their own). This is a good optimizing technique when your images always have specific dimensions, but is a bit too limited if the images respond to the end-user’s viewport size. When you also need to take the viewport into account, you need another technique: responsive images.

Combining these two techniques was my ultimate goal and because I could not find any scripts that did this, I decided to build my own: prImages.

prImages – progressive responsive images

prImages is a JavaScript prototype function (still in development) I created that allows for progressive and responsive image loading. It was written with phpThumb in mind as an image-generating backend, but can easily be adapted to either pre-rendered images, or another resizing/thumbnail library.

How it works

prImages watches for page load, browser resizing and scrolling to do its work. Only visible elements are processed (this can be disabled entirely, or per-image). These elements initially load a low-quality version of the original image (this is not handled by the script, but should already be in the HTML).

Then, for all images, processing is done as follows:

  1. The script checks if the image is currently in view. If it isn’t, it is not considered for (re)processing.
  2. The script then measures the width of each image container – either a div (as background attribute) or an img – then matches that to common responsive width breakpoints (160, 320, 480, 768, 1024, 1224, 1824 pixels), keeping aspect-ratio in consideration if specified. Matching is done upwards; say an image’s container is 300px wide, an image with 320px width is selected. If the user’s device has a high dpi screen (such as retina screens for Apple devices), this value is doubled.
  3. On basis of the calculated width, a new url is composed.
  4. The new image is pre-loaded
  5. The new image is injected into the html as a replacement of either source or background attribute.

On each event (resize or scroll), the images are reprocessed and adjusted if necessary. As it is a prototype script, each image has its prImage-variables stored, so there is not much overhead on reprocessing. Also, when the container requires a width larger than the image’s original dimensions, no processing is done and the original image is loaded instead.
Width-determination is quite rigorous: if the script cannot determine a width, it will look for all the image’s direct siblings (with the proper classname) if a similar node is found *with* width and use that. If even that fails, it will continue to traverse the DOM upwards (max 3 times, but this can be changed) looking into parent elements until it finds a width. This covers 99% of situations where no width can be determined for the image’s container.

I have created a separate page on which three scenarios are explained.

Demo

I use prImages on this website (hnldesign.nl) for all images.

You can also check out this demo gallery.

The gallery is a demonstration of prImages, with both div and img elements and debug-info (feedback) on changes and dimensions. Resize, scroll and reload the page to see it in action. Disable cache to see the progressive part on each refresh, disable JavaScript to see the graceful fallback.

Advantages

Especially when set-up using an image processor such as phpThumb, you require zero effort for your (responsive) images: everything is generated (and cached) on the fly. Only the first time someone (you or a visitor of your site) views an image, there will be a small delay while the images are resized/cropped.

Disadvantages

As it is JavaScript, it will not work for visitors that have JavaScript disabled. To accommodate this, you will have to resort to a server-side script, such as PHP, and use some trickery to ‘detect’ JS support and present a non-JS user non-scaled-down images:

// redirect if no JavaScript support, and redirect back if JavaScript support<br />
define('JavascriptDisabled', ($_SERVER['QUERY_STRING'] === 'nojs') ? true : false);<br />
if ($_SERVER['QUERY_STRING'] !== 'forcenojs') {<br />
    if (!JavascriptDisabled) {<br />
        //'redirect' non js users to the same url with query string nojs<br />
        echo '&lt;noscript&gt;&lt;meta http-equiv="Refresh" content="0;url=http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'].'?nojs"&gt;&lt;/noscript&gt;';<br />
    } else {<br />
        //redirect back if js is available (again) while the 'nojs' query string was set (this strips it from the uri)<br />
        echo '&lt;script language="JavaScript"&gt;window.location="http://'.$_SERVER['HTTP_HOST'].strtok($_SERVER['REQUEST_URI'],'?').'";&lt;/script&gt;';<br />
    }<br />
}

Code

View/fork the code on Github: https://gist.github.com/c-kick/7cd27db9c9350d186a4b

Usage

As I have not intended prImages to be a universal, easily implementable script, but more as a tool for my own work, setting it up can be a bit difficult perhaps. But I’ll try to explain the basics as best I can here:

Include the script into your page (don’t load it asynchronously though). Make sure it is the first script that the browser will load after your content. This makes sure image (pre-)loading is done immediately without waiting for other script libraries to load. As it is pure JavaScript, it has no dependencies.
Specify your responsive images as follows (just the bare essential tags) :

<img src="<full-URL-to-image>" width="<width>" data-alwaysprocess="<true or false>" data-imgprocessor="<URL>" data-path="<PATH>" data-ratio="<ratio>" data-srcwidth="<width>" data-crop="<true or false>" class="primage">
  • The class (primage) is essential, as the script will select elements using that class.
  • data-alwaysprocess will determine if the image is processed even if it is not in view (yet). Useful for carousels.
  • data-imgprocessor full URL to the root of your image processor, such as phpThumb. Note that this is only necessary when you use query-based urls for your images. (See below).
  • data-path the path of the image RELATIVE to the image processor, eg the path that will be sent to your img processor url (resulting in something like image.php?f=../../img/myimage.jpg)
  • data-ratio the ratio of the image (height/width)
  • data-srcwidth original width of the source image (thus maximum width)
  • data-crop wether or not to crop the image (setting a height in accordance with the aspect ratio, and setting the cropping flag for the processor)

I highly recommend automating this using a server-side script (in PHP, for example)

Further required configuration

Specify if you are using rewritten urls or query urls by setting the UrlType in prImages to either ‘uri’ or ‘query’.
Specify the way you want prImages to construct these url’s inside the buildUrl function. Per default, it will construct the image url as:

path/width/height/quality/crop/filters/filename.extension

For query-based url’s; configure the options inside the QueryVars variable.

Optional configuration

You can specify a few other variables inside prImages:

  • HighQuality Quality setting of the replacement images to load (0-100), defaults to 90.
  • WatchResize Whether to execute prImages on browser resize (this is debounced). Defaults to true.
  • WatchScroll Whether to execute prImages when the user scrolls the page (this is debounced). Defaults to true.
  • WatchLoad Whether to execute prImages when the browser fires the ‘DOMContentLoaded’ event (when all html and CSS content is loaded). Defaults to true.
  • OnlyVisible Whether to process only images visible in the viewport. Defaults to true.
  • SearchSiblings Whether to search siblings (see above) if a width cannot be determined
  • ResBrPts The responsive breakpoints to match to. Defaults to [160, 320, 480, 768, 1024, 1224, 1824],
  • DpiMultiplier The multiplier used for high dpi device-screens. Defaults to (window.devicePixelRatio >= 1.5) ? 2 : 1.

Disclaimer

As said, I never intended this script as a publicly available script, but purely as a personal tool. This implies that the script may contain errors with unforeseen situations, so please excuse me if that happens 😉