Rails has made it possible to enable browser built-in lazy-loading of images across your whole app.
Read on to learn about the feature and what to consider before using it.
The background
Until recently web browsers loaded all images on the page eagerly, even when hidden far off the page. That means lots of wasted bytes since the visitor downloads images they never end up seeing.
For supporting browsers, native lazy loading will only load images when they are about to scroll in to the viewport.
This progressive enhancement seems like a big win. Less wasted energy. 🌎❤️
How to setup app-wide lazy image loading in Rails
In Rails you can specify config.action_view.image_loading
(in your
app or environment specific config) with either lazy
or eager
.
Rails.application.configure do
config.action_view.image_loading = "lazy"
end
Setting to "lazy"
(or :lazy
) will mean all HTML image tags generated by the Rails image tag view helper
will have the loading="lazy"
attribute. This will tell supporting browsers to lazy-load the images.
Equally setting to "eager"
(or :eager
) will mean image tags will have the loading="eager"
attribute, i.e.
the browser to use the current eager loading behaviour (which is the default loading strategy).
The Deep Dive: But why eager
as default?
There are some concerns about having the default image loading to be “lazy”, both at browser level and in Rails.
Lets dive deeper…
1. Privacy concerns 🛡
First there are privacy/fingerprinting concerns for browser vendors.
Browser developers have a really tough time. It seems to be a constant fight to stay ahead of tracking attempts.
Eg, the HTML spec on lazy loaded resources suggests browser devs hide the true scroll speed of the user for this reason:
🤯
2. The dreaded Layout shift
Then there is layout shift, the annoying jumping of content on the page when something changes.
In this case, if the user is scrolling and an image is lazy-loaded, but isn’t ready by the time the user scrolls to its position, they may see the layout change when it finally does pop in.
The general recommendation to prevent layout shift is:
- always ensure images have their
width
andheight
attributes specified on the HTML tag (or use CSS).
But it is often the case that these attributes have not been specified.
Setting action_view.image_loading
to "lazy"
will set the image to lazy load even if there are no dimensions specified.
From web.dev article on the topic:
Why care about Layout Shift (CLS)?
- layout shift is annoying for viewers,
- lots of layout shift may affect your search engine ranking from this year when CLS is added as a metric for user experience.
3. Lazy loading affects “preload”
The Chromium team recommends to not lazy-load images that will start off in the viewport
(ie images high up on the page or “above the fold”) due to the loading
attribute preventing “preloading” of images.
Addy Osmani has written up all about preload. Essentially the preload declaration tells the browser to make requests for resources without blocking the document’s onload event.
But as the browser scans for resources it might preload, it will skip any that are marked as loading="lazy"
.
This may affect the so called “time to interactive” of your page by disabling the preload optimisation on those images.
A higher TTI may affect user experience and ranking in the same way as CLS.
Why care about Time to Interactive (TTI)?
- waiting to interact with a page is annoying for viewers,
- long TTI may affect your search engine ranking from this year when TTI is added as a metric for user experience.
Conclusions
To enable the built-in browser lazy-loading, specify config.action_view.image_loading
with the value "lazy"
. But note the caveats discussed above.
Personally I will be enabling the feature as soon as possible and evaluating CLS (Cumulative Layout Shift) and TTI (Time to Interactive) to check for any degredation in real life usage. User experiece and search rank is important, but our planet more so.
If you found this article useful, why not follow me on Twitter for more.
Extra notes
You can use native lazy-loading without the config
Of course you can set HTML attributes on images already by passing to image_tag
:
<%= image_tag "pic.png", loading: "lazy" %>
The main point of the new config is to allow this to be set application wide.
What about browsers that don’t support native lazy-loading
Browsers which don’t support the loading
attribute will safely just ignore it.
You can add a JavaScript polyfill if you wish, for example this polyfill which uses lazysizes
by the web.dev team.
The lazy loading attribute is not image tag specific
It is actually defined as part of the “Fetching resources” section of the “Common infrastructure” part of the HTML specification. For example, apart from images it is also defined for use with iframes
.
Browsers disable native lazy loading when JavaScript is disabled
If JavaScript is disabled by the user, lazy loading is too. The HTML spec tells us why:
The loading
attribute is not enabled by default in Safari
While enabled already in Chrome and Firefox, the feature is still experimental in Safari, both on MacOS and iOS… hopefully it will follow suite and be enabled by default soon.
Recommended Reading
- The feature pull request at
rails/rails
- The excellent “Browser-level image lazy-loading for the web” on
web.dev
- The HTML spec on it
- Supporting browsers
Whats next?
Synchronous / Asynchronous Image Decode support for image tags!