Search

TL;DR - Have forked BBC News' Responsive Imager.js to make it work with Symphony's JIT and added Retina/Hi-DPI support.

About a month ago the BBC News wesbite team (who are working on a new responsive design) released Imager.js as one solution to the Responsive Image problem. It relies on having an URL image service to resize images on the fly - luckily for us Symphony comes with JIT. Unfortunately Imager.js wasn't playing nicely with JIT due to different URL structures - so I've tweaked it so it now works out of the box with JIT. I've also added support for serving up Retina/Hi-DPI/@2x images when the client can handle it, and included a simple XSLT template to output the correct Imager.js HTML snippet without having to mess around with URLs.

Github repo: https://github.com/firegoby/Imager.js

There's more details about the Symphony specifics & changes at the top of the README. Btw, you don't need the whole repo, just the Imager.js file and imager.xsl - see the Demos/Docs from the original Imager.js for general usage instructions. I've also created a gist with the basic XML and XSLT

Sorry but I don't have either a spare server and/or time to setup an online demo just at the moment, perhaps at the weekend, but hopefully it should be fairly easy to setup locally if you're interested in having a play around with it. Anyway, perhaps it's of interest/use to someone :)

Note: All credit goes to the BBC team, all bugs are probably me :)

Very cool :)

This sounds pretty cool. Just so I can get my head around the extension though, in the BBC post they say:

The way we determine what size image to place onto the page is by taking the current screen width and then looking at the ‘rendered’ size of our image (using myImageElement.clientWidth). We then check if the rendered image size approximately matches one of the sizes available in our predefined list of image dimensions. Once we have a match we then parse the current image URL (which if using a RESTful format would look something like http://your-image-service.com/horse/100/ -> and would return an image of a horse 100px wide), finally we replace the width in the URL with the new replacement width and update the image source to load that more appropriately sized image based from the new RESTful URL we set as the image source.

But in your example I see that you're setting a fixed width:

<xsl:template match="//images/file">
    <xsl:call-template name="imager">
        <xsl:with-param name="image" select="."/>
        <xsl:with-param name="width" select="900"/>
    </xsl:call-template>
</xsl:template>

Is this required for a base image or is this just optional? I thought that the main idea with imager.js was to serve up an unknown image size based on width according to viewport size? Setting it specifically seems to lose that dynamic value(?).

@ijy - Yeah sorry I should have been more clear, the width param is just used by Imager.js as the width of the placeholder it puts on the page, not the actual final image. I'll have to check but I think they use the rendered width of this within the page to help determine which is the best current size. If the div with width = 900 as in my example is contained within another div i.e. as part of a grid etc then the image placeholder reduces in size to fit, so yes everything is responsive, your thought about how it works is indeed right.

I've started work on a demo page, it's only very basic at the moment but definitely makes things clearer (I hope!) check it out. I'll be adding to it soon but if you look at the source should give you an idea of how simple it is to implement. As you resize the page the current JIT URL is displayed so you can see what's being requested from the server.

If you've got a retina screen you'll get served 2x images, if you then move your browser window to a non retina screen (e.g. external monitor) you'll notice you'll switch to just getting regular images.

One thing I'm going to add is a debouncing function so that there's a 200ms or so delay on the resize event, at the moment when playing with the demo occasionally I get a mis-calculated or mis-served image, I think perhaps because the browser is kicking off so many requests to the server that things get confused?? Probably OK with the BBC's mega server setup but on my little VPS system not so much! But yes, it doesn't need to be recalculating that much anyway so a nice debounce should help reliability/performance.

I've fleshed out the demo page a bit, with multiple images and a simple responsive layout (1col-2col-1col).

Also added a debouncing function to Imager.js so the size calculations are only done once every 200ms (overridable via options). Also tied the Retina check into that too so that check is no longer run once for each image, but once only per resize. All the new stuff has been push to the github repo

I'm still experiencing very occasional times when the wrong width is calculated, can't seem to figure out why, perhaps in part because I'm resizing the window like a madman and not like a usual user of a browser! Luckily it usually fails upwards, i.e. sources a too-large image which is no worse than would happen on any other website that doesn't handle responsive images. Still, it would be nice to make it super reliable.

Out of all the Responsive Image solutions out there at the moment, this combo of Imager.js and Symphony certainly seems like a good one to me, simple to implement, retina support and very-little/near-zero config. Just upload one master image and the rest is pretty much automated :)

Does the regex cope with JIT crop modes? I.e. if you wanted to have a crop for width and height before outputting to the frontend?

This looks really promising.

At the moment no, it's hard-wired to mode 1 - but multi-mode support (or at least working out what is possible/practical) is definitely on the road-map. As a first stage I just wanted to get the BBC's version working (which only requests images defined by width) - but as a 2nd stage I'd love to see if more complex JIT transforms/recipes can be made to work too.

A quick follow-up to the demo notes above - in my testing I only seem to experience those incorrect widths issues when dynamically resizing the page (i.e. dragging the corner) - if the page is resized and then refreshed (i.e. simulating a normal page load) then Imager.js has been spot-on. So if anyone gets different results than that I'd be very interested to hear about it. The nice thing about all this is it's not a critical bug, even if it screws up and gives the wrong width once in a blue moon users just get a slightly heavier/lighter image than perfect - in the world of web development problems this is one I can happily sleep easy at night over :)

Cool beans. Tried your test page on mobile and rotation/window.resize from portrait to landscape results in correct image switch on a Samsung S3.. great work!

Oh nice! Thanks for trying that out, hadn't got to mobile testing but awesome news it worked, and rotation too! Cheers :)

This is awesome :) In under 5 minutes I now have responsive images on the site I'm developing. Works great on iphone retina screen and ipad. Thanks so much!

Thanks Dave! Great to hear it was so easy to implement! (and loving that it's working well on iphone/ipad). Would be very interested to hear whether you find having the debouncing function enabled or not (debounce: false in options to Imager.js) makes it more or less reliable - I'm of mixed feelings, in playing around with it this evening I'm starting to worry that the debouncing function is actually less reliable than without, but would be very keen in learning what others think, especially when using it within real designs. Best of luck with your site! Thanks again :)

@firegoby - Super cool!!!!

Awesome! Great job on this— I'll definitely be using it for an upcoming project.

Just thinking aloud: I'm curious how feasible it'd be to load new images in the background before doing the src replacement, just as a way to avoid the blank flash during image loads.

Thanks guys!

@brockpetrie - Preloading the images though would defeat the purpose, the aim is to load the least amount of page assets (i.e. don't load a big 2000px image when the client can only display 320px) - while I understand the desire to avoid the blank flash I can't see how that can be reconciled with minimising image downloads. Also, remember the blank flash is mostly an issue when people are actively resizing their browsers, which is an edge case - sure there is a blank flash on a normal page load while the JS loads and determines the correct image size but that can't be overcome until browser makers give us a native responsive image solution (picture element, srcset etc) - in the meantime I think the BBC team has come up with the best practical solution to the problem.

Btw - I'm about 90% convinced that the debouncing function is actually hurting reliability, just based on my playing with the demo - I've setup an alternative demo page with the debouncing disabled in case anyone wants to test both versions. I think I trust the BBC team's coding skills a lot more than my own! :D

Update - Been exploring the issues with occasional mis-calculated widths and the (un)reliability of the debouncer, turns out there wasn't an issue with either, rather it was a race-condition between Imager.js and my rather hastily put together demo page!! (An object lesson in making sure your test suite is actually testing the right thing and accurately!) I've changed the main demo page to wait half a second before updating the JIT URLs and everything is working super smoothly. I'll leave the debouncing function in place (it's easily disabled with debounce: false if need be anyway) because it's not harming reliability and if the script is used in real-world designs with lots of other JS running it seems like a Good Citizen thing to do to only run as much as is necessary.

@firegoby, I'm not saying that all assets should be preloaded, but rather than replacing the src when a breakpoint is hit, it'd be nice to quietly load the new image in the background, then replace the src. This would basically involve an invisible container, loading the image inside of that invisible container, and binding the src replace to the load event of the hidden image.

Super narrow edge case—I think using progressive jpegs would largely solve this issue anyway— but interesting to think about. It's easy in theory, I'll do some playing.

@brockpetrie - Oh right! I see what you mean, sorry I didn't quite realise what you meant before. So basically just keep the existing image until such time as the newly requested image is actually ready to display... mmm interesting idea! And yes would completely solve the blank flash problem. Not sure what the performance implications might be though on a larger page with lots of images, do you think adding all those invisible DOM elements and extra logic would be too heavy, or would it be ok? I guess with the debouncing function now working there is more runtime available to do extra stuff behind the scenes...

Btw, talking of progressive JPEGs, I think my master images were saved as progressives, does anyone know if JIT is capable of generating progressive JPEGs as that would be good to implement too

One thing I have noticed when testing on iphone and ipad is the aforementioned blank flash when changing orientation from portrait to landscape or vice versa. I agree with @firegoby that in most cases the blank flash doesn't matter because users don't generally mess with their browser size whilst browsing but it is more likely to occur on mobile or tablets if the user changes the orientation.

Is there a way of preloading images in this case or downloading just one image that would be suitable for both orientations?

@davecoggins - I agree with you that mobile/tablet reorientation is the #1 case where users would be genuinely affected by the blank flash. There isn't any preloading option at the moment, that's what is covered by Brock's idea above.

The only way to prevent blank flash currently would be to customise your image sizes so that they both fall within the same 'best width' and then Imager.js will serve the same image - but you would have to take a bit of wasted bandwidth as the price. For example, if the mobile portrait design called for a 250px image, the mobile landscape for 350px, desktop 700px and you wanted retina support then setting your availableWidths to [350, 700] should cover you, since the 350px would be used for both mobile orientations and no blank flash would occur. (on retina devices both would be served 700px I think). But then you're serving a 350px wide asset to a 250px wide design requirement, which starts to undermine the point of the plugin, but yes at the moment that's the way to do it if the blank flash had to be avoided. Hope that makes sense :)

Thanks for the clarification @firegoby. I guess at the moment its a trade off that has to be made on a project by project basis.

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details