How to stop content jumping when images load?
Images are widely used in most websites, web apps and PWAs, have a look at any of your recent projects and it’s likely that images are responsible for a good portion of its bundle size.
Because of this you might have run into this scenario: you have a mix of images and text based content in your page but, as images load, the whole layout shifts, jumps and changes providing a very poor user experience.
This is particularly worse on mobile devices due to the limited screen sizes and connection speeds. Also it doesn’t help that modern best practices recommend lazy loading images below the fold because it means the jumping and shifting will continue as users scroll down the page.
Just set a width and height on the images?
Oh if only life was that easy, the truth is that setting the width
and height
of images does let the browser “reserve” the image space before it loads as it knows how big it will be ahead of time, however there’s a huge caveat, it doesn’t work at all if the image is responsive.
In most cases you’ll have CSS like this to make sure images will scale down nicely:
img { max-width: 100%; height: auto;}
That height: auto
will “nullify” the fixed height
attribute, with good reason, and now the browser have no way of knowing how big the image needs to be without loading it.
The aspect ratio padding trick
Images scale up and down based on its aspect ratio, so knowing this we can use of the oldest CSS tricks to solve this problem.
The aspect ratio padding trick is often use to help with responsive videos and SVGs, it relies on the fact that padding in percentages is based on the element width, which can help us with the content jumping problem.
All we need is to wrap the images in a zero height container and give a padding-top
with the same aspect ratio of the image, eg:
You don’t even need to use calc
here it just helps visualise it better, all you need is to get the image aspect ratio based on the following formula:
(height / width * 100
Now as you can see on the following pen we’ve avoided the dreaded content jump, even when images take a while to load:
BONUS: Sprinkle some JS on top
Since a lot of us are using some sort of component-based javascript framework we can make a new Img
component to help reuse our new images, we can also add some lazy loading features and fancy loading indicators just for fun.
I’ll be using ReactJS in this example but it should also be doable in your framework of choice, the basic concepts will be the same:
It looks more complicated but in essence it’s the same idea as the static html/css counterpart. The .wrapper
element will act as the padded container and the .img
element will be absolutely positioned inside.
We could have used inline styles to set the padding-top
on the container based on the image size but by using CSS variables we’ve added an extra buzzword to our component which is nice to have this day and age.
I’m also using the react-intersection-observer library that provides a render prop with an inView
parameter that lets me know when the image is inside the viewport and added some CSS animations to show a loading indicator when images are being loaded.
In the end our Img
component can be used like any other img
element:
<Img width={360} height={360} src="https://loremflickr.com/360/360/cat" alt="Cute cat"/>
With the benefit that now we have a reusable image component that includes:
- Lazy loading.
- Avoids content jumps.
- Loading indicator.
Feel free to play around with the demo and improve it as you see fit, feedback is always welcome! 👋🏽