React
stop content jumps

Stop content jumps

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:

import React from "react";
import PropTypes from "prop-types";
import Observer from "react-intersection-observer";

import styles from "./Img.module.css";

export default function Img(props) {
const { src, alt, width, height, …rest } = props;

return (
<div className={styles.root} style={{ "–maxWidth": width }}>

{({ inView, ref }) => (
<div
ref={ref}
className={styles.wrapper}
style={{
"–height": height,
"–width": width
}}
>
<img
src={inView ? src : ""}
alt={alt}
width={width}
height={height}
className={styles.img}
{…rest}
/>

)}

);
}

Img.propTypes = {
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired
};
view rawImg.js hosted with ❤ by GitHub

.root {
max-width: calc(var(–maxWidth) * 1px);
}

.wrapper {
position: relative;
height: 0;
padding-top: calc(var(–height) / var(–width) * 100%);
background: #f6f7f8;
background: linear-gradient(to right, #fafafa 8%, #f4f4f4 38%, #fafafa 54%);
background-size: 1000px 640px;
animation: placeHolderShimmer 1.8s linear infinite forwards;
}

.img {
position: absolute;
top: 0;
left: 0;
max-width: 100%;
height: auto;
vertical-align: middle;
}

@keyframes placeHolderShimmer {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
view rawImg.module.css hosted with ❤ by GitHub

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.

 

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.

img

img

https://codesandbox.io/s/1642lz7n7

Feel free to play around with the demo and improve it as you see fit, feedback is always welcome! 👋🏽

ITNEXT

ITNEXT is a platform for IT developers & software engineers…

 

Scroll to Top