Rails recently introduced a new helper - picture_tag
.
This adds a tag helper picture_tag
which generates a <picture>
tag with <source>
and <img>
tags.
Why picture tag?
As we build modern web applications,
we need to support different devices with different screen sizes and resolutions.
We also need to support different image formats like webp
and avif
for better performance.
Although most applications still rely on jpeg
or png
images,
which in Rails, can currently be served by the image_tag
easily.
Responsive applications serving these images to different devices and browsers,
generally use srcset
and sizes
attributes of the <img>
tag.
Here’s a simple example making use of srcset
attribute to provide different images for different screen sizes.
<div class="box">
<img
src="/en-us/web/html/element/img/clock-demo-200px.png"
alt="Clock"
srcset="/en-us/web/html/element/img/clock-demo-400px.png 2x" />
</div>
This still leaves us slow loading of images sometimes, as we try to load a larger images with better resolution. As well as the images may be cropped as we move across devices, requiring portrait mode or landscape mode images and so on.
The picture tag helps us solve some of these problems
and provides a better way to serve images.
It can consists of a series of source
tags with different srcset
and media
attributes,
and a fallback img
tag if none of the source
tags match.
<picture>
<source srcset="/car-240-200.jpg" media="(orientation: portrait)">
<img src="/small-car-298-332.jpg" alt="">
</picture>
Art Direction
The problem being solved here is a technique called Art Direction where we can provide different images for different screen sizes and orientations.
Art direction: The problem whereby you want to serve cropped images for different layouts — for example a landscape image showing a full scene for a desktop layout, and a portrait image showing the main subject zoomed in for a mobile layout. You can solve this problem using the
<picture>
element.
Resolution Switching
The second problem that also is being helped by the <picture>
tag is Resolution Switching
Resolution switching: The problem whereby you want to serve smaller image files to narrow-screen devices, as they don’t need huge images like desktop displays do — and to serve different resolution images to high density/low density screens. You can solve this problem using vector graphics (SVG images) and the
srcset
with sizes attributes.
Rails picture_tag helper
Rails 7.1 now supports the picture_tag
helper which generates a <picture>
tag with <source>
and <img>
tags.
A simple example is as follows:
<%= picture_tag("car.jpeg") %>
This generates the following HTML:
<picture>
<img src="/images/car.jpeg" />
</picture>
Multiple sources
For providing multiple image sources we can do as follows:
<%= picture_tag("car.webp", "car.avif", "car.png",
:image => { alt: "Image", class: "responsive-img" }) %>
This generates the following HTML:
<picture>
<source srcset="/images/car.webp" />
<source srcset="/images/car.avif" />
<source srcset="/images/car.png" />
<img alt="Image" class="responsive-img" src="/images/car.png" />
</picture>
Complete example with custom tags
We can also use custom tags- source
and image_tag
to build a custom picture
tag,
by passing a block to picture_tag
helper-
<%= picture_tag(:class => "responsive") do %>
<%= tag(:source, :srcset => image_path("car.webp"), media => "(min-width: 600px)") %>
<%= tag(:source, :srcset => image_path("car.avif"), media => "(min-width: 600px)") %>
<%= tag(:source, :srcset => image_path("car.png"), media => "(min-width: 600px)") %>
<%= image_tag("picture.png", :alt => "Image") %>
<% end %>
This generates the following HTML:
<picture>
<source srcset="/images/car.webp" media="(min-width: 600px)" />
<source srcset="/images/car.avif" media="(min-width: 600px)" />
<source srcset="/images/car.png" media="(min-width: 600px)" />
<img alt="Image" class="responsive-img" src="/images/car.png" />
</picture>
Summary
The picture_tag helper in Rails is a great addition to support modern web image formats and responsive images out of the box.