theTechnaddict

Live: now, wherever; make, learn, beHappy.

Swapping Source of Responsive Images in Dart

When building a responsive site it isn’t enough to change the dimensions of images to match the window, we also need to think about the mobile user’s data usage. Bootjack does a great job of keeping images resized, but what do we do to serve up the right image size for each device? In this post I’ll code up one method, then reject it in favour of another.

It is pretty easy to make images resize to fit the available space on a window – Bootjack, the Dart port of Bootstrap 3 has a helper class for that:

.img-responsive {
  display: block;
  height: auto;
  max-width: 100%;
}

Pretty standard: image shrinks as the viewport shrinks, but building sites that not only cater for different screen sizes but also consider the different circumstances of the people using those devices is a little more involved; here’s a case in point:

I’m building a simple site for an artist. Let us think of two people who may be interested in visiting it: first there’s Jan, an art student who thanks her dyslexia for giving her the grant that enabled her to buy an iMac with a 27inch retina display. An essential part of Jan’s training as an artist is to look at art, and she has heard of an exhibition she can’t get to, so she hops along to the artist’s site eager to fall into the gorgeous images that are going to fill her screen and her world.

Next up is Johan, a gallery owner visiting from Berlin. He too has heard about this great show with an as-yet unsigned young artist he is eager to visit. He’s already cursing Telefonica: although they provide international data-roaming for just two euro a day, his iPhone, which he relies on for boarding passes, maps, for hotel bookings and almost everything else, gobbles up the daily data allowance in seconds. Now he is visiting the artist’s site to get hold of his contact details… will his allowance get used up before he can say ‘hi’?

Is my implementation of the site going to blow my artist’s career break? Which user is more important, the dealer or the future of art?

I need to dish up massive, sumptuous images to the art student with her megafast broadband and huge screen, and a just-right-sized, skinny file to the visiting dealer who only wants a quick contact.

Being lazy (and using this as a means to avoid the opportunity to introduce errors) I want to use something that is already going on. Media queries are at the heart of Bootstrap, so my first instinct was to look for a way to use the media queries to give me a indication of the device being used and to set the src property of the img tag to load an image of the right size.

I found this post on source shuffling based on media queries by Jordan Moore that was doing just that, so it seemed that all I had to do was translate that approach into Dart and sit back in delight.

The first question on my mind was how do I change the src of the img tag programmatically in Dart – this shows the advantage of a language designed for web development over one that has evolved:

var images = querySelectorAll('img');
images.forEach((image){
     image.src = whatever;
});

Where ‘whatever’ is whatever you want the image src to be…

The next question was how to detect which media query is being used. I started with the technique discussed by Jeremy Keith for conditional css that uses the pseudo element with some content that is not displayed.

At first I thought that Dart was unable to detect pseudo elements. Later I realised that, unlike JavaScript, it is unable to access properties that are not displayed, So instead we can use visibility: none – I am not quite sure what disadvantages this approach has – perhaps someone can comment?

The CSS:

/*==========  Mobile First Method  ==========*/

  /* Custom, iPhone Retina */ 
  @media only screen and (min-width : 320px) {

  }

  /* Extra Small Devices, Phones */ 
  @media only screen and (min-width : 480px) {
    body::after {
      content: 'phone';
      visibility: hidden;

    }
  }

  /* Small Devices, Tablets */
  @media only screen and (min-width : 768px) {
    body::after {
        content: 'tablet';
        visibility: hidden;
    } 

The dart code for a function that returns the media query that is active…

String getMediaQuery(){
  String mediaQuery = document.body.getComputedStyle('::after').getPropertyValue('content');
  return mediaQuery;
}

So putting it all together…

We set up the html following Jordan Moore’ suggestion:

<img class="image-responsive" 
    src="images/stunning_painting-sm.jpg" 
    data-sm="images/stunning_painting-sm.jpg" 
    data-md="images/stunning_painting-md.jpg" 
    data-lg="images/stunning_painting-lg.jpg">

The dart code:

void main() {

    window.onLoad.listen((e){
        setImgSrc();
    });

    window.onResize.listen((e) {
        setImgSrc();
    });

}

String getMediaQuery(){
  String mediaQuery = document.body.getComputedStyle('::after').getPropertyValue('content');
  return mediaQuery;
}

void setImgSrc() {

  var images = querySelectorAll('img');
  String mediaQuery = getMediaQuery();

  if (mediaQuery.contains('phone')) {
      images.forEach((image){
        image.src = image.dataset['sm'];
      });
   }
   if (mediaQuery.contains('tablet')){
      images.forEach((image){
        image.src = image.dataset['md'];
      });
   }
   if (mediaQuery.contains('desktop')){
      images.forEach((image){
        image.src = image.dataset['lg'];
      });
   }
   if (mediaQuery.contains('large-wide')){
      images.forEach((image){
        image.src = image.dataset['xl'];
      });
   }
}

So now we have something that works: we start with a small image (mobile first); test to see what media query is being used, then load the best image size for that device.

It works, but is it any good? Well, no, for these reasons

  • it first loads the mobile image, for everyone – so the large screen is first shown the small image then the large. Quite apart from the inefficiency it is ugly. We might be able to use the animation library to do something about smoothing the transition (you can’t use css transitions on an img src), but that isn’t really the point. We want to be able to present the right image first time. And notice that the function that sets the source is triggered by a change in window size – so it isn’t really about the device being used simply the size of the browser window. If like me you tend to use quite a small browser then make it full screen to view the gallery it will load three lots of images.
  • It is a really expensive – the html has to have three src’s for each image and you have to prepare three images – for each one. Who wants to do that?

I don’t think we can detect media query in use before the img src is set the first time – perhaps someone can correct me on that, but that belief set me off looking for something else.

Günter Zöchbauer who seems always to be on hand to offer help on stackoverflow, pointed out that I could use data-binding to do the job. He suggested creating a responsive image tag (using Polymer one assumes) with the source bound to values that you set in some way. It seemed a little complex, when at the time I was following Jordan Moore’s src shuffling approach, but is something that I shall return to later.

Meanwhile I found this from Matt Wilcox:‘adaptive images’

You dump device information into a cookie and use an .htaccess rewrite to send every image request to a server script that reads the cookie, prepares the image for the device and returns it in place of the image requested.

I like the directness of the approach and the fact that it doesn’t require additional html code, nor additional image preparation. Chuck the images in and let the server do the work.

The php implementation is a doddle to set up, customise and it works well and fast even on the first runs (before there are cached files).

I should port it into Dart, and perhaps I shall. One day. It should be easy enough – the php code clean and comments exemplary, Dart is great with files and images, and perhaps we could go further and set it all up as a Polymer component with data-binding… But all on another day.

For now my priority is to deliver the solution for my artist on a server that will support the php solution so in it goes. It feels messy to combine languages, but it works a treat.

Comments