Right, quick tip. You know how every website you've
ever built probably loads the same massive image
regardless of whether the user is on a 27-inch iMac
or a phone being held together with optimism and a
cracked screen protector? Yeah, let's fix that. Here's
how to generate a <picture> element
in Rails that tells the browser to stop being an idiot
and load the right image for the right screen.
The <picture> element is one of
those HTML features that's been around long enough that
you have absolutely no excuse for not using it. It lets
you specify multiple image sources with media queries,
and the browser picks the first one that matches. It's
like a buffet, but the browser only takes what it can
actually eat. Revolutionary concept, I know.
So here's a helper you can drop into your Rails app. Takes about thirty seconds to write, saves your users from downloading photographs the size of small countries:
# application_helper.rb
module ApplicationHelper
def responsive_image_tag(image, options = {})
content_tag(:picture) do
concat content_tag(:source, nil, media: '(max-width: 768px)', srcset: image.url(:thumbnail_mobile))
concat content_tag(:source, nil, media: '(max-width: 960px)', srcset: image.url(:thumbnail_tablet))
concat image_tag(image.url(:thumbnail_desktop), options)
end
end
end
That's it. That's the whole thing. You wrap your
<source> elements inside a
<picture> tag, slap some media
queries on them, and the browser does the rest. The
media queries here are standard breakpoints —
768px for mobile, 960px for tablets — but
obviously you can change those to whatever arbitrary
numbers your designer has decided are gospel this week.
A <picture> element can take any
number of <source> children and
will load the first one that matches the current
screen. That's the beauty of it. You're not doing
any clever JavaScript viewport detection nonsense.
You're not loading all three images and hiding two of
them with CSS like some sort of bandwidth arsonist.
The browser genuinely only fetches the one it needs.
Now, the image argument I'm passing into
this helper is a
CarrierWave
uploader object. If you're not familiar, CarrierWave
lets you define different versions of an
uploaded image — so one upload gives you a mobile
thumbnail, a tablet thumbnail, and a desktop version,
each at the appropriate resolution. The
.url(:thumbnail_mobile) calls are just
fetching the URL for each version. If you're using
ActiveStorage or Shrine or whatever the cool kids have
moved onto this month, the principle is exactly the
same — you just swap in the equivalent method
calls.
The fallback image_tag at the bottom is
your standard <img> element. It's
what loads when the browser doesn't support
<picture>, which at this point
basically means Internet Explorer, and if your users
are still on IE, responsive images are the least of
their problems. Or yours.
If you want to go deeper on the
<picture> element — and you
should, because it's genuinely one of the better things
to happen to HTML since they stopped trying to make
<marquee> a thing — have a
look at the
MDN documentation
and the
Can I Use
page for current browser support. Spoiler: it's
basically everything.
-
Use the
<picture>element with<source>children and media queries to serve the right image for the right screen - Wrap it in a Rails helper so you write it once instead of copy-pasting HTML like it's 2004
- The browser only downloads the matching source, so your users on mobile stop subsidising your enthusiasm for 4K photography