Karl Castillo
Posted on March 29, 2020
Loading states in images are a nice way to tell your visitors that an image is currently loading. For us to show a loading state, we need to specify the size of the image.
What if we don't know the size but we know that we want our image to be a certain aspect ratio? We can take advantage of math to calculate the size of our image!
Since it's much easier to determine the width of an element, we'll be using that to calculate our loading state size.
We'll use this formula to calculate the height:
const height = (width / ratioWidth) * ratioHeight
Let's make our Image component by figuring out which props
we want to be looking out for.
const Image = ({ alt, aspectRatio = "16:9", onLoad = () => null, ...rest }) => { ... }
We need alt
specifically because of Linting rules. The aspect ratio will be what we'll use to do our calculations. We could also split it up into 2 prop, ratioWidth
and ratioHeight
. Lastly, we'll be watching out for onLoad
since we'll be hijacking the img
default onLoad
. We want to make sure that we can still pass an onLoad
prop into our component.
We'll need to keep track of a couple things to make our loading state possible -- the state if the image has loaded and the height of our loading box.
const [hasImageLoaded, setHasImageLoaded] = useState(false);
const [containerHeight, setContainerHeight] = useState(null);
Now that we have those setup, we can now calculate for the height of our loading state.
const containerRef = useRef(null)
useEffect(() => {
if(containerRef.current) {
const [ratioWidth, ratioHeight] = aspectRatio.split(':')
const height = (containerRef.current.offsetWidth / ratioWidth) * ratioHeight
setContainerHeight(height)
}
}, [aspectRatio, containerRef]
return (
<div ref={containerRef} style={{ height: containerHeight }}>
...
</div>
)
Now that our scaffolding is ready, let's build our DOM!
const onLoad = (event) => {
setHasImageLoaded(true)
onLoad(event)
}
return (
<div className="image-wrapper" ref={containerRef} style={{ minHeight: containerHeight }}>
{currentHeight && (
<>
{!hasImageLoaded && <div className="image-loading"></div>
<img
{...rest}
alt={alt}
onLoad={onLoad}
className="image"
/>
</>
)}
</div>
)
We're wrapping the image in a container which will be used to contain the loading state div.
Let's look at our CSS. Feel free to use whatever animation you want to signify loading.
@keyframes loading {
from {
opacity: 0.9;
}
to {
opacity: 0.5;
}
}
.image-wrapper {
position: relative;
width: 100%;
line-height: 0;
}
.image-loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #aaaaaa;
animation: loading 1s infinite linear running alternate;
}
.image {
position: relative;
width: 100%;
max-width: 100%;
}
Some notable things in the CSS are the fact that we're setting the position of our image-loading
element as absolute so we can have it behind the image as well as having the size be 100% width and height of our image-wrapper
.
Now that our component is done, what usecase do have for it? Maybe an image gallery?
Posted on March 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 26, 2023