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}
/>
)}