
You've seen it at least a couple of times already: Those shiny box effects when images take a little longer to load. They're on news sites, blogs, shops, you name it.In this article, I'll explain how to build a component in Vue that offers this effect!
Some scaffolding
I'll start with a new component I callLoadingBoxImage.vue
- this will be a single file component, so it can be used within any Vue or Nuxt app afterwards. First, I'll basically wrap the<img>
tag:
<!-- LoadingBoxImage.vue --><template><img:src="src":alt="alt"/></template><script>exportdefault{props:{src:{type:String,required:true},alt:{type:String,required:true}}}</script>
Now I'll add some styling to this, so it behaves well and more responsive:
<stylescoped>img{max-width:100%;}</style>
This component can be used like this:
<!-- App.vue --><template><divid="app"><loading-box-imagesrc="http://via.placeholder.com/3200x2400"alt="Some image"/></div></template><script>importLoadingBoxImagefrom'./components/LoadingBoxImage'exportdefault{components:{LoadingBoxImage}}</script>
So far, so good. I've replicated the standard image tag as a Vue component.
Next up: Adding a box and removing it again.
The placeholder
The placeholder box will be an aria-hidden div next to the image. I'll hide it once the image has loaded via the nativeload
event and a flag:
<template><divclass="image-container"><img:src="src":alt="alt"@load="loaded"/><divclass="image-placeholder":class="{ hidden: isLoaded }"aria-hidden="true"/></div></template><script>exportdefault{props:{src:{type:String,required:true},alt:{type:String,required:true}},data(){return{isLoaded:false}},methods:{loaded(){this.isLoaded=true}}}</script><stylescoped>.image-container,img{max-width:100%;}.image-placeholder.hidden{display:none;}</style>
I also needed to add a container around the image and its placeholder and did some adjustments to the styling.
Now, ideally, the placeholder should be the same size as the image, right? I've got two options here: Use fixed dimensions or try to fetch them before the image has fully loaded. Since the latter sounds a lot fancier, I'll implement this first.
At some point, the image will havenativeWidth
andnativeHeight
available, so I can use those to calculate an aspect ratio:
// LoadingBoxImage.vue, script partmounted(){constimg=this.$refs.img// Constantly poll for the naturalWidthconstsizeInterval=setInterval(()=>{if(img.naturalWidth){// As soon as available: Stop pollingclearInterval(sizeInterval)// Calculate image ratiothis.loadedAspectRatio=img.naturalWidth/img.naturalHeight}},10)}
(I also addedref
attributes to the original<img>
tag and the placeholder to be able to fetch the necessary data)
I can use that now to calculate the placeholder's height. To make it more responsive, I'm updating the client width on the window'sresize
event and set it once when mounted:
// ...data(){return{isLoaded:false,loadedAspectRatio:null,clientWidth:0,};},methods:{loaded(){this.isLoaded=true;},updateClientWidth(){this.clientWidth=this.$refs.placeholder.clientWidth;}},computed:{/** * Calculates the height of the placeholder * via the images nativeWidth and nativeHeight. */placeholderHeight(){if(!this.loadedAspectRatio){return0;}returnthis.clientWidth/this.loadedAspectRatio;},},mounted(){constimg=this.$refs.img;constsizeInterval=setInterval(()=>{if(img.naturalWidth){clearInterval(sizeInterval);this.loadedAspectRatio=img.naturalWidth/img.naturalHeight;}},10);window.addEventListener('resize',this.updateClientWidth)this.updateClientWidth()},// ...
And set this on the placeholder:
<!-- ... --><divclass="image-placeholder":class="{ hidden: isLoaded }"aria-hidden="true"ref="placeholder":style="{ height: `${placeholderHeight}px` }"/><!-- ... -->
Ok, now I've got a placeholder the same size as the image! Awesome! Now I can start to...
Add the shiny box effect
This can be done with CSS keyframes and linear gradient:
.image-placeholder{background:rgba(0,0,0,.2);background-image:linear-gradient(120deg,rgba(255,255,255,0)0%,rgba(255,255,255,0)40%,rgba(255,255,255,0.8)50%,rgba(255,255,255,0)60%,rgba(255,255,255,0)100%);background-position-x:-100vw;background-repeat:no-repeat;animation:shiny1.5sinfinite;}@keyframesshiny{0%{background-position-x:-100vw;}10%{background-position-x:-100vw;}75%{background-position-x:100vw;}100%{background-position-x:100vw;}}
This will add a single reflection that moves periodically from left to right to an otherwise grayed element.
And that's it!
Here's a Codesandbox to see it in action (I'm not hiding the placeholder for you to see what it would look like):
I'm sure that the gradient and the timing can still be tweaked, though. Also some use cases, like smaller images and complete accessibility are missing, but I'm certain that this can be used as a starting point.
Happy holidays!
I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a ❤️or a 🦄! I write tech articles in my free time and like to drink coffee every once in a while.
If you want to support my efforts, please considerbuying me a coffee ☕orfollow me on Twitter 🐦!
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse