Image component: Avoid layout shift on image load

The library lazysizes has some recommendations to solve this:

It also has a plugin to use an aspectratio attribute. We can take a look at the code to understand what it does.

It looks like the Gutenberg team is going to solve the markup mess of the Image block: https://github.com/WordPress/gutenberg/issues/20650

I guess this will simplify the implementation for the fact that the aspect ratio can’t be applied directly to the img tag.

  • No Gutenberg -> add wrapper
  • Gutenberg -> use wp-block-image wrapper

Is that right guys?

It seems like there has not been much movement in this issue since November: https://github.com/WordPress/gutenberg/issues/20650 so maybe we should not wait anymore and try to fix this, as @orballo has proposed in this other thread

@cielosky42, could you help us here by making a list of the different variations of the current Gutenberg Image block, similar to the one I did a year ago here: Image component: avoid jumps on image load? Thanks!! :slightly_smiling_face:

Hi @luisherranz,

I will work on that. Thanks for looking into this.

Best,
Sky

Awesome, thanks :grinning_face_with_smiling_eyes:

I just run into this today. Maybe it’s a thing to take into account:

2 Likes

@luisherranz After going over this again, it does not appear that there have been any changes to the standard image markup since you documented it above. I think I was confused by the inconsistent image markup that depends on alignment.

I’ve further narrowed down issues with align left/right/center and non-aligned images.

For align left/right:

The images don’t display when floated left or right. The images themselves are absolutely positioned, and none of its parents have an explicit width set. If I set an explicit width on the wrapping figure element, the image is visible and is able to scale as needed when the constrained by the screen width.

The span in between the figure and img elements has both “width” and “height” properties set. However, this syntax is not valid on spans, only images. Was this meant to be added as CSS properties instead?

If so, I am seeing issues when trying to set the CSS of the span with the width and height. If I try going that route, the image does not display in the right aspect ratio as soon as there isn’t enough room for the full image size. It becomes distorted, or doesn’t scale, or has a bunch of extra padding, depending on other CSS added.

I’m not sure about all the other scenarios listed at the top of this thread, but as far as just adding an image block with left or right alignment, the only thing that works for me without running into the issues I mentioned is if I set an explicit width on the figure with a max width of 100%.

For aligncenter and images with no alignment:

Aligncenter and images with no alignment are filling the entire available space, greater than the width should be for the given size. Setting an explicit width on the figure also resolves the problem here.

Looking at the HTML2react image processor, I don’t see a way to modify the wrapping figure element, nor a way to target the image markup differently based on alignment.

Other than the previously mentioned CSS that overrides the “padding hack”, I was also able to “fix” this by targeting the size classes added by Gutenberg, which get added to the figure element. However, this requires adding specific styles for each avaialable image size. It would be nice if these extra steps weren’t necessary.

Here’s example CSS to fix things for the “medium” image size:

figure.wp-block-image.size-medium,
div.wp-block-image .size-medium {
  width: 300px;
  max-width: 100%;
}

Lastly, the images in the Gallery block suffer from these same issues. I was not able to apply the same fix as above with the gallery, because there aren’t image size classnames added to the figures in this case. The only solution I found here was to override the “padding hack”.

2 Likes

Thanks a lot for the detailed analysis @cielosky42 :slightly_smiling_face:

Thanks to you as well @orballo. Very interesting, although it seems like we would have to wait a while until all major browsers have support for this, right?


In my opinion, we have two different use cases here:

  1. The Gutenberg images inside of content.
  2. The other images added by the theme in React, like the Feature Image in a post or an archive.

For case 1, I see no reason right now to do anything on top of what WordPress+Gutenberg is already doing.

I’ve been taking a look and it seems like Felix Arntz added a fix to WordPress 5.5 to always include the image dimensions and avoid a layout shift:

I am not sure this is a 100% solved problem yet and there are still some related issues open in Gutenberg:

But my point here is: Why should we spend time solving an issue on top of WordPress if it can be solved in WordPress and there are people already working on solving it in WordPress? 🤷

@santosguillamot @cielosky42: what happens if we remove the image processor and simply use the Gutenberg CSS? Are the problems solved?


For case 2, I think we could provide an Image component that solves this for the rest of the images. Maybe we can design it to make it impossible to cause a layout shift.

I am not sure about the requirements for that, but I guess it would require at least one prop that it can use to figure out the space that needs to be reserved:

  • A fixed height.
    Used in conjunction with width: 100% to calculate the responsive size.

  • An aspect ratio.
    With the same value as the upcoming CSS prop (https://web.dev/aspect-ratio/) but used internally to calculate the padding hack value.

  • A media ID.
    So the Image component can access state.source.media[id] and get the width and height from there.

This is just an example. I am not a CSS expert by any means and less with this image size sorcery, so maybe I am wrong in my assumptions.

EDIT: This seems like a great resource to learn how to calculate the padding hack using an aspect ratio input: Aspect Ratio Boxes | CSS-Tricks

Can we use this one code for ACF fields in image . because i have passed id value but i didn’t get attachment data from state.source.attachment[id] API.

Implement lazy loading for images, which defers the loading of offscreen images until the user scrolls near them. This can significantly improve page load performance and minimize layout shifts by loading images only when they are needed.