How to use the picture element to prevent images loading on mobile devices.
A major part of responsive design is all about providing the best user experience possible regardless of the device being used.
Sometimes, this comes in the form of hiding images whilst on smaller devices to save both bandwidth and screen real estate.
For example, you may have a listing page of event signups which have accompanying thumbnail images showing a snapshot of the location.
For those on smaller screens though, we may want to remove the images so that we don’t have to sacrifice vertical screen real estate.
From a development point of view, this can be easier said than done. If we want to work with progressive enhancement in mind, we want to work from the lowest common denominator up. That means looking straight at the HTML.
What about CSS or JavaScript i hear you say? Let’s have a quick look at these approaches.
CSS
@media screen and (max-width: 768px) {
img {
display: none;
}
}
Why can we just use media queries when on mobile devices to add a display none to an image? It removes the image from the UI, problem solved right?
Not quite. The issue with this approach is that CSS will talk a lower priority than the HTML, meaning your image will be loaded even if it isn’t shown. That’s not very fair on our mobile users who may have limited data.
div {
background-image: none;
}@media screen and (max-width: 768px) {
div {
background-image: url(some-awesome-image.jpg);
}
}
Another solution is to serve the image as a background image inside a media query, so that it only loads at the screen size we want it to. Now that is definitely a perfect solution if the image in question is an actual background image, but what if it isn’t?
If the image is contextual to the pages content, we want to let the browser know about it, and we also what to give our screen reader users a description of the image, which isn’t possible via CSS. This solution also starts getting messy when you introduce a CMS into the mix, or want to use responsive images.
JavaScript
JavaScript solutions to this problem tend to require the <img> element to do away with the src attribute and replace it with a “data-src”. There are also methods that keep the initial src attribute, but use it to load a spacer image.
<img data-src=”some-awesome-image.jpg” alt="Awesome image">
Issues with this are that the code is no longer valid (may not be a concern for some) and users with JavaScript disabled will see no image. The latter issue can be resolved with a <noscript> tag containing the image, but then the image would still be loaded on mobile devices without JavaScript enabled, plus we end up with a bit of duplication which some may find a bit irksome.
This technique is often used when we want to lazy load an images, which isn’t quite what we are trying to do here either. It also creates an external dependency that we may not actually want.
So what hair-brained idea have I come up with?
The Picture Element
The picture element had been around for a few years now and is supported in all modern browsers (IE will need a polyfill if you need to support it, but will fail gracefully if you don’t want to include it).
The purpose of picture element is to provide a native way to handle art direction with images. That means we can serve multiple image sizes and/or different images depending on the screen sizes, dpi and a number of other variables the browser will take into consideration under the hood.
This got me thinking, could we use this to our advantage, and load either no image or a pixel sized image when we don’t actually want to load one.
With that in mind, I came up with this.
<picture>
<source media=”(max-width: 767px)”
sizes=”1px"
srcset=”data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 1w”/> <source media=”(min-width: 768px)”
sizes=”300px”
srcset=”
./some-awesome-image-600.jpg 600w,
./some-awesome-image-300.jpg 300w”/> <img src=”./some-awesome-image-300.jpg” alt=”This is an awesome image”/>
</picture>
What is happening here is when the user is on a browser with a screen size of 767px or smaller (which is when we want to hide the image) we instead load a base64 encoded image (which is about 78bytes). As this is loaded when the html is parsed, the browser doesn’t need to open up a request as the image is already cached. This means if a person is on a smaller screen, no extra requests will be made for an image, but the alt tag will still be accessible. If a person is on a larger screen they will get served the image as they normally would, plus we can take advantage of the srcset element and give them a retina image if they are on a high resolution screen.
This technique works well if you only need to do this once or twice in a page. Once we start having say 15 images, we can swap out that base64 encoded image with a blank image (a transparent, 1px by 1px gif works really well). That way the tiny image is loaded once then cached, and we save a few kb file size in out HTML because we don’t need to right out the base64 image each time.
<picture>
<source media=”(max-width: 767px)”
sizes=”1px"
srcset=”blank.gif 1w”/> <source media=”(min-width: 768px)”
sizes=”300px”
srcset=”
./some-awesome-image-600.jpg 600w,
./some-awesome-image-300.jpg 300w”/> <img src=”./some-awesome-image-300.jpg” alt=”This is an awesome image”/>
</picture>
Boom there we have it, no more unnecessary file download when we don’t want it. This method also means we can take advantage of native browser functionality and our screen users can still get information about the image. It also works with JavaScript disabled and isn’t relying on CSS.