Imager.js is an alternative solution to the issue of how to handle responsive image loading, created by developers at BBC News.
There are many responsive image solutions in the wild: srcset
, src-n
, PictureFill
and so on. They are either
verbose or hard to debug (and to maintain/integrate). Some of them don't deal well with pixel density
and suffer from double asset payload (meaning you end up downloading assets unnecessarily).
We wanted something simple, which works and which is fast as well as network friendly (only download what you need, when you need it).
Imager implements the BBC Responsive News technique which incorporates:
- loading any image once
- loading the most suitable sized image
Imager runs through the following workflow:
- lookup placeholder elements
- replace placeholders with transparent images
- update
src
attribute for each image and assign the best quality/size ratio URL
Finally, it will lazy load images to speed up page load time even further.
npm | bower | old school |
---|---|---|
npm install --save imager |
bower install --save imager |
download zip file |
<div style="width: 240px">
<div class="delayed-image-load" data-src="http://placehold.it/{width}" data-alt="alternative text"></div>
</div>
<script>
new Imager({ availableWidths: [200, 260, 320, 600] });
</script>
This will result in the following HTML output:
<div style="width: 240px">
<img src="http://placehold.it/260" data-src="http://placehold.it/{width}" alt="alternative text" class="image-replace">
</div>
<script>
new Imager({ availableWidths: [200, 260, 320, 600] });
</script>
260
has been elected as the best available width (as it is the closest upper size relative to 240
pixels).
<div style="width: 240px">
<div class="delayed-image-load" data-src="http://example.com/assets/{width}/imgr{pixel_ratio}.png" data-alt="alternative text"></div>
</div>
<script>
new Imager({ availableWidths: [200, 260, 320, 600] });
</script>
The img[src]
will be computed as following (according to the reported window.devicePixelRatio
value by the device):
http://example.com/assets/260/imgr.png
if no pixel ratio is detected, or advertised as1
http://example.com/assets/260/imgr-2x.png
if pixel ratio is advertised as2
http://example.com/assets/260/imgr-1.3x.png
if pixel ratio is advertised as1.3
Head to this device pixel density test resource to learn more about the available pixel ratio for your device.
Imager has the ability to replace {width}
with a non-numeric value if you provide the availableWidths
option/value in the Object
type. This feature allows you to use a human readable name or integrate with third-party images provider.
<div style="width: 240px">
<div class="delayed-image-load" data-src="http://example.com/assets/imgr-{width}.png" data-alt="alternative text"></div>
</div>
<script>
new Imager({
availableWidths: {
200: 'square',
260: 'small',
320: 'medium',
600: 'large'
}
});
</script>
The img[src]
will be computed as http://example.com/assets/imgr-small.png
instead of http://example.com/assets/imgr-260.png
.
You might want to generate HiDPI responsive images. But what if you also include images from another provider which serves a totally different set of sizes, without pixel ratio?
Here is an example to serve your own images alongside Flickr images.
<div style="width: 240px">
<div class="delayed-image-load" data-src="http://placehold.it/{width}" data-alt="alternative text 1"></div>
<div class="delayed-flickr-image-load" data-src="//farm5.staticflickr.com/4148/4990539658_a38ed4ec6e_{width}.jpg" data-alt="alternative text 2"></div>
</div>
<script>
var imgrPlaceholder = new Imager('.delayed-image-load', {
availableWidths: [200, 260, 320, 600]
});
var imgrFlickr = new Imager('.delayed-flickr-image-load', {
availableWidths: {
150: 't_d',
500: 'd',
640: 'z_d'
}
});
</script>
This will result in the following HTML output:
<div style="width: 240px">
<img src="http://placehold.it/260" data-src="http://placehold.it/{width}" alt="alternative text 1" class="image-replace">
<img src="//farm5.staticflickr.com/4148/4990539658_a38ed4ec6e_d.jpg" data-src="//farm5.staticflickr.com/4148/4990539658_a38ed4ec6e_{width}.jpg" alt="alternative text 2" class="image-replace">
</div>
The HTML API helps you control how Imager works from the content point of view.
Available placeholders are:
{width}
: best available image width (numeric value){pixel_ratio}
: device pixel ratio (either blank or-1.3x
,-2x
,-3x
etc.)
So the following HTML...
<div data-src="http://placehold.it/{width}" data-alt="alternative text"></div>
...is converted to...
<img src="http://placehold.it/260" data-src="http://placehold.it/{width}" alt="alternative text" class="image-replace">
data-width
is the enforced size of the image placeholder; where the actual image will eventually be loaded.
This can be especially useful if you don't want to depend on the image container width.
So the following HTML...
<div style="width:600px">
<div data-src="http://placehold.it/{width}" data-width="300" data-alt="alternative text"></div>
</div>
...is converted to...
<div style="width:600px">
<img src="http://placehold.it/300" data-src="http://placehold.it/{width}" width="300" alt="alternative text" class="image-replace">
</div>
data-alt
is the alternative text for the image and should provide equivalent content for those who cannot process images or who have image loading disabled. It is converted to the alt
attribute of the img
element.
The JavaScript API helps you instantiate and control how Imager works from a business logic point of view.
Calling the constructor will initialise responsive images for the provided elements
or the HTML elements concerned by the selector
.
The options
bit is an object documented below, in the JavaScript Options section.
new Imager('.responsive-image-placeholder');
The constructor can be saved in a variable for later use...
var imgr = new Imager('.responsive-image-placeholder', { onResize: false });
// Using jQuery to set-up the event handling and help keep the correct scope when executing the callback
$(window).on('resize scroll.debounced', $.proxy(imgr.checkImagesNeedReplacing, imgr));
For legacy reasons the first argument is optional and defaulted to .delayed-image-load
:
new Imager();
Updates the img[src]
attribute if the container width has changed, and if it matches a different availableWidths
value.
It is relevant to use this method if an unwatched event occurred and impacts responsive image widths.
var imgr = new Imager();
// Using jQuery to set-up the event handling and help keep the correct scope when executing the callback
$(document).on('customEvent', $.proxy(imgr.checkImagesNeedReplacing, imgr));
Registers a window.onresize
handler which will update the relevant img[src]
(using Imager.checkImagesNeedReplacing
)
when the window size changes.
This covers window resizing, device orientation change and entering full screen mode.
var imgr = new Imager();
// Using jQuery to set-up the event handling and help keep the correct scope when executing the callback
$(document).on('load', $.proxy(imgr.registerResizeEvent, imgr));
Registers a window.onscroll
handler which will update the relevant img[src]
(using Imager.checkImagesNeedReplacing
)
when the content is scrolled.
A default 250ms debounce is performed to avoid
trashing the rendering performance. You can alter this value by setting the scrollDelay
option.
var imgr = new Imager();
// Using jQuery to set-up the event handling and help keep the correct scope when executing the callback
$(document).on('load', $.proxy(imgr.registerScrollEvent, imgr));
This option is intended to reflect the available widths of each responsive image. These values will be used as replacements
for the {width}
and data-src
placeholders.
The following examples demonstrate the results of passing through different object types for the availableWidths
option...
Array
: the widths are represented as numeric values
new Imager({
availableWidths: [240, 320, 640]
});
Object
: the widths associate a string value for their numeric counterpart
new Imager({
availableWidths: {
240: 'small',
320: 'medium',
640: 'large'
}
});
Function
: must return a value for the provided width argument
// will return a double sized image width as a numeric value
new Imager({
availableWidths: function (image) {
return image.clientWidth * 2;
}
});
A String which indicates what the className
value will be added on the newly created responsive image.
new Imager({ className: 'image-replace' });
Default value: image-replace
An Integer value (in milliseconds) to indicate when Imager will check if a scroll has ended. If a scroll has stopped after this delay and the lazyload
option is true
, Imager will update the src
attribute of the relevant images.
Default value: 250
new Imager({ scrollDelay: 250 });
Notice: set the scrollDelay
value to 0
at your own risk; unless you know what you're doing, setting the value to zero will make the user experience totally janky! (and that would be an odd thing to do as you have chosen to use Imager to improve the user experience)
A Boolean value. If set to true
, Imager will update the src
attribute of the relevant images.
Default value: true
new Imager({ onResize: true });
An experimental Boolean value. If set to true
, Imager will update the src
attribute only of visible (and nearly visible) images.
Default value: false
new Imager({ lazyload: true });
This demo requires the following commands to be run...
npm install
(all dependencies specified in package.json)brew install imagemagick
(for other installations see http://www.imagemagick.org/script/binary-releases.php)
Review the Gruntfile.js
and update the custom sizes that you want to use (if no sizes are specified in the Gruntfile then 320, 640, 1024 are used)...
options: {
sizes: [
{
width: 320,
height: 240
},
{
name: 'large',
width: 640
},
{
name : 'large',
width : 1024,
suffix : '_x2',
quality: 0.6
}
]
}
...be aware the names of the files need to change within your HTML...
<div class="delayed-image-load" data-src="Assets/Images/Generated/A-320.jpg" data-width="1024" alt="alternative text A"></div>
<div class="delayed-image-load" data-src="Assets/Images/Generated/B-320.jpg" data-width="1024" alt="alternative text B"></div>
<div class="delayed-image-load" data-src="Assets/Images/Generated/C-320.jpg" data-width="1024" alt="alternative text C"></div>
You can then pass those image sizes through to Imager.js along with a regex for Imager to parse the information...
var imager = new Imager({
availableWidths: [320, 640, 1024]
});
For full details of the Grunt task options see the grunt-responsive-images repo on GitHub.
This is an experiment in offering developers an interim solution to responsive images based on the ImageEnhancer concept researched and developed by the team at BBC News.
At present, support for srcset
and PictureFill
are not widespread and the polyfills for these solutions also come with a number of drawbacks.
Mark McDonnell (@integralist) documented the process and rewrote the original code so it could be evolved and improved with the help of the open-source community.
The goal of this project is to automate the process with the help of the Grunt JavaScript task runner (potentially via grunt-responsive-images
for image generation based on a source directory).
Much of this work can be repurposed to work with a more standards-based approach once support improves in modern browsers.
For the purposes of maintaining a distinguishment between the ImageEnhancer concept built by BBC News and this project, we're calling it Imager.js
Copyright 2013 British Broadcasting Corporation
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.