RFC: Image Component Blurry Placeholder #24004
Replies: 5 comments 3 replies
-
I think for certain scenario like card lists or image lists a skeleton placeholder would be great. |
Beta Was this translation helpful? Give feedback.
-
@Joonpark13 I like this RFC of the I have a great doubt about the placeholder, there are cases in which developers do not want to work as medium (blurred placeholder) does, but rather we would like to be able to add a component of type Skeleton. Given this, I propose to see the option that the Image component can receive a fallback prop so that the developer also has the freedom to show that he wants to paint. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the mention of Plaiceholder! I'm gutted we couldn't find a way to work together to resolve the remote images/configuration concerns mentioned, but I appreciate there are other caveats to consider; such as its dependency on This was a great read, and I'm really looking forward to seeing this SVG filter solution in action. Thanks for your hard work on this 🙏 |
Beta Was this translation helpful? Give feedback.
-
Waiting for this for a long time. My project is in beta but planning to go GA only when th is feature comes. I'm not sure about the possibilites but here are some of my problems which you may add a factor while solutionizing
|
Beta Was this translation helpful? Give feedback.
-
Excited to see this feature roll out as soon as possible 😍 |
Beta Was this translation helpful? Give feedback.
-
Update: This has shipped! https://nextjs.org/docs/api-reference/next/image#placeholder
Introduction
We want to add a “blur-up” style image placeholder feature to the Next.js Image Component (next/image). Currently, lazy-loaded images are just blank space (preventing layout shift) until the image is loaded and rendered. We would like to provide the option to render a placeholder, similar to the one popularized by Medium.
Motivation
Background
Objectives
Non-Objectives
Design
At a high level, the general approach for most of the approaches described in the Background section is the same: take a tiny version of the image and scale it up + blur it. Then display this placeholder where the real image would appear on the page, until the real image loads. The details for each of these steps will be discussed in detail in the sections below - the summation of our implementation choices will ultimately result in a solution most closely described by https://www.industrialempathy.com/posts/image-optimizations/#blurry-placeholder.
Acquire the tiny version
The tiny version of the image will be included in the initial html inside of the img tag’s styles through a data URL. We prefer this instead of fetching a tiny version of the image in order to remove the extra request. The data URL itself will be provided to the Image Component as a prop either during static generation or server side rendering. The details on how this data URL will be generated by the server will be discussed separately and are not in scope for this doc.
Placeholder size
How to determine the exact size of the tiny image can be hashed out separately alongside the details for generating its data URL. However, it’s evident through some prototype testing (see codepen here) that we can expect it to be under 1000 bytes, likely in many cases well under that.
Apply blur + scale up
Instead of using CSS to blur and scale up the tiny placeholder image as is done by many of the community solutions mentioned above, we will place the placeholder image’s data URL inside of an SVG. This SVG itself will take up the full size of the real image and will handle the blur as well as the scaling of the placeholder image. Using an SVG blur instead of a CSS blur means that the blurring is only performed once per image when the SVG is rasterized, instead of on every layout, saving CPU. This SVG, in turn, will be included in the data URL for the
background-image
style of the img tag.The details of the SVG blur itself can bee seen at https://css-tricks.com/the-blur-up-technique-for-loading-background-images/#recreating-the-blur-filter-with-svg. At a high level, the SVG blur filter includes a
feGaussianBlur
and afeComponentTransfer
to fix transparency issues at the edges of the image. See https://output.jsbin.com/weboyaj/3/quiet for a demonstration of why thefeComponentTransfer
is required.Implementations of the blurry placeholder image that uses CSS to scale the image up will not have a large improvement on LCP since LCP takes into account the placeholder image’s intrinsic size, which is much smaller. While our implementation would not suffer from this since the actual placeholder data URL is wrapped in the svg’s full-size data URL, this gain in LCP is actually an unintentional side-effect of the LCP implementation. The Chrome team currently has efforts to assess the implementation in order to specifically make sure that blurry placeholders are not counted in LCP calculations.
BlurHash has been brought up as a possible smaller alternative to data URLs. Since we know that base64-encoded is successfully deployed at scale, we will err on the side of having been proven at scale. This can be another possible iterative feature that we may circle back on.
Display placeholder
The placeholder image will be included as a
background-image
style of the img tag of the original image. This removes the need for any additional elements in the DOM, as well as styling that would have been required to correctly position and render the real image in conjunction with the placeholder element.Display real image
Thanks to our implementation of the placeholder image as the
background-image
of the img tag itself, the real image will automatically hide the placeholder image when it loads. However, we will still set thebackground-image
tonone
when the real image loads in case the real image contains any transparency. This also prevents the browser from needlessly rasterizing the placeholder image after the real image has loaded. See https://www.industrialempathy.com/posts/image-optimizations/#(optional-ish)-javascript-optimization for an example JavaScript snippet that achieves this.Exceptions
Very small images
When the size of the image (given by the width and height props) is very small, we should not create a blurry placeholder for it. Since images with layout set to “fill” will not have a given width or height, we can enable the feature for all images with layout set to “fill”. The exact threshold should be based on the average size of the placeholder image itself (see Placeholder size section.
Lazy loaded images
Accurately predicting a blurry placeholder’s utility for lazy loaded images is very difficult. Theoretically, lazy loaded images should be visible by the time the user scrolls the image into the viewport, and therefore would never need a blurry placeholder. However, there are practical cases where this isn’t always the case (Ex: user scrolls extremely quickly down a long page of many images, like an image-heavy blog) where preloaded blurry placeholders would make a positive impact. On the other hand, loading blurry placeholders on all images of an image-heavy blog page may negatively impact the initial HTML load size.
Since at this stage it isn’t clear whether the additional size in initial HTML would be worth accounting for these special cases, we will only show blurry placeholders where they will have the most noticeable impact: above the fold. We’ve had difficulties accurately gauging which elements are within the viewport in the past, we will use the priority attribute as a substitute appropriateness indicator. We should circle back after the first pass to assess whether a better indicator is required.
Image Component API changes
Ultimately we want to provide this feature to all users of the Image Component by default, without any additional configuration or prop necessary. However, an escape hatch prop should be provided in order to allow users to turn this feature off if they wish. Since we may want to support multiple image placeholder types in the future, we will do this by adding a
placeholder
prop to the component. If this prop is set to “empty”, the Image Component should resort to its current behavior (render blank space of the correct size until the real image is loaded). We won’t be providing an app level config to toggle this feature, though we may revisit this if there is enough user demand.We can later circle back to see if there is a need for additional features that can be configurable, such as allowing users to choose the fidelity (and thus the size) of the blurry placeholders, or even allowing users to specify any secondary behavior that we may decide to implement with lazy-loaded images (such as alternate placeholder fetching methods).
Performance Measurements
While the desire for the feature from the community & the Next.js team is the strongest driver of this work, we also consider it a goal for this feature to improve site performance. With the help of our partners, we will need to measure any improvements in web vitals or any other relevant performance metrics. As discussed in the Apply blur + scale up section above, any improvements in LCP created by this feature would be an unintentional side effect and will likely be patched out in future implementations of LCP calculations.
We can additionally compare the performance of our implementation as compared to the various existing community implementations listed in the Background section through side-by-side comparison tests.
Documentation Changes
Beta Was this translation helpful? Give feedback.
All reactions