In this technical post, we describe optimisation methods which made our responsive website load twice…
Why we don’t use picturefill for responsive images (for now)
After a seemingly endless wait, <picture>
is firmly establishing itself as the solution for responsive images to come. That’s great news, because the web badly needs a standard solution for handling images in responsive contexts to improve performance. But using <picture>
in your design and development workflow will still leave you with major drawbacks.
Lets have a quick view at the picture syntax to show why:
<picture>
<source srcset="large.jpg" media="(min-width: 800px)">
<source srcset="medium.jpg">
<img srcset="medium.jpg" alt="Alt text">
</picture>
Imagine changing breakpoints
The big advantage of the above syntax compared to pure srcset attributes is that you can use familiar CSS media queries to define breakpoints. This is probably one of the reasons why this proposal gathered so much support. But imagine modifying existing breakpoints or adding new ones in your web project. You might have to adapt every single picture
element modifying the media
attribute to your new values. Especially when you work with CSS preprocessors like SASS or LESS and want to keep your code DRY using variables for your breakpoint definitions. <picture>
removes this flexibility because breakpoints have to be maintained in HTML.
Or using relative units
Additionally, we define breakpoints using rem units in our projects. However, the Picturefill script processing picture
elements evaluates media queries using the browser’s native window.matchMedia
API. Feeding it with rem
values results in different breakpoint values across browsers. Some (Safari) consider the document root font size set by CSS while others (Chrome) ignore it and stick to the browser’s default font size of 16px. At the moment, using rem
for breakpoints is therefore not a safe option when you’re using <picture>
.
So what are our options?
Grunt as a pre-pre-processor
Using <picture>
and still maintaining a central definition for breakpoints could be achieved e.g. by the task manager Grunt and a preprocessor like grunt-include-replace that replaces variables with breakpoints in SASS and HTML. This means adding another step of preprocessing before the SASS is parsed and thus adding more overhead to the process. Also breakpoint definitions and CSS would still be separated. As this does not feel like a practicable solution, we went with a different one.
CSS-only
The behaviour of <picture>
can be replicated with pure CSS defining responsive background images for a div
element. This technique is working on all modern browsers and allows to globally maintain all breakpoints within your SASS/CSS. The major downside of this solution is the violation of correct document semantics forcing a div
element to behave like an img
element, hurting SEO and accessibility. To mitigate this, the role
and aria-label
attributes can be added to the element:
<div id="my-image" class="responsive-image" aria-label="this is my image" role="img">
<div class="responsive-image__inner"></div>
</div>
The actual definition of the responsive images takes place in SASS, with the help of this handy SASS mixin we created:
#my-image
{
// @include responsive-image(img__url, img__width(px), img__height(px), breakpoint);
@include responsive-image("my-image.jpg", 400, 249);
@include responsive-image("my-image--medium.jpg", 750, 467, $medium-breakpoint);
@include responsive-image("my-image--large.jpg", 1024, 638, $large-breakpoint);
}
Why we have moved the image sources into the CSS code? You’re right, we’re in a dilemma, either having to hold our media queries in HTML or our image sources in CSS. If you are dealing with a CMS and dynamic file paths, the CSS-only solution will definitely not make your life easier.
What, if …
A perfect solution for our needs would keep image sources in the HTML and media queries in the CSS. That could be accomplished quite easily by managing visibility of picture sources through CSS classes. At the moment though the current RICG draft for the <picture>
and <source>
element does not include class attributes.
Imagine the following:
<picture>
<source srcset="large.jpg" class="show-large-only">
<source srcset="medium.jpg" class="show-medium-only">
<img srcset="medium.jpg" alt="Alt text">
</picture>
.show-medium-only,
.show-large-only
{
display: none;
}
@media (min-width: $medium-breakpoint) and (max-width: $large-breakpoint)
{
.show-medium-only
{
display: inherit;
}
}
@media (min-width: $large-breakpoint)
{
.show-large-only
{
display: inherit;
}
}
The server side
This approach could be further improved by involving the server to manage simple image resizing and recompressing based not only on viewport size but also on bandwidth. This would mean simple resizing of images could be negotiated between server and browser without extra markup, based on the visible size of the image, its pixel density and the available bandwidth. Classed picture sources would only be needed where there are differences in art direction, such as cropping or replacing images. HTTP-Client-Hints seems to go in that direction, but involving the server obviously means that a standard for handling responsive images is even harder to achieve.
So there’s a lot more work to be done until there’s a definite solution for responsive images. For now, CSS is the best solution for us, but we’ll keep a close eye on <picture>
to see how it evolves.
I honestly think that responsive image and tags are among the most annoying changes being standardized and forced into mark up. It’s just painful to have to rethink embedding an image in a page when for as long as I can remember it was a one tag, one attribute process.
I guess things always have to change but you would think there would be a simpler way to format these things. As you pointed out, CSS in HTML is “against the rules”, and it’s absurd to me that you now have to create a tree of elements just to add an image to a page as well as create separate image sizes and formats to make some speculative end-user happy.
All I can say is, thank god for Grunt, and I wish I had the knowledge to create a preprocessor so I wouldn’t have to bother with it so often.
I love the idea of being able to take the media queries out of the html and put them back into css. I think this may interfere with how aggressive browsers are about prefetching images, but it would be a cleaner approach.
Yes, you’re completely right. Sensible prefetching is rather difficult to implement with information on visibility sitting in CSS. Splitting that CSS into a file that’s being loaded early on as part of the critical path might help some, but still won’t be able to compete with prefetching that’s done before the DOM is even completely parsed.
Nice article – we don’t like the idea of defining media queries on html element base too – so we came up with a solution based on anzeixer.js – check it out here: http://www.andre-abt.com/2013/09/16/responsive-breakpoints-and-images/