Lazy loading your images greatly improves the performance of your web app. In this article i will show how easy it is to make it work in Vue.
Full component
<template>
<img
class="lazy-image"
:class="{ 'lazy-image--loaded': loaded }"
:src="loadedSrc"
@load="isLoaded">
</template>
<script>
export default {
props: {
src: {
type: String,
default: null
}
},
data() {
return {
loadedSrc: null,
loaded: false
}
},
mounted() {
this.observer = new IntersectionObserver(this.loadImage, {
root: null,
rootMargin: '1000px',
threshold: [0, 0.25, 0.5, 0.75]
})
this.observer.observe(this.$el)
},
methods: {
loadImage(event) {
if (event) {
let isIntersecting = event.find(entry => {
return entry.isIntersecting
})
if (!isIntersecting || this.loaded || this.loadedSource) {
return
}
}
this.loadedSrc = this.src
if (this.observer) {
this.observer.disconnect()
}
},
isLoaded() {
this.loaded = true
}
}
}
</script>
<style lang="scss">
.lazy-image {
background-color: #eee;
display: inline-block;
width: 100%;
height: auto;
opacity: 0;
visibility: hidden;
transition: opacity 1s ease;
pointer-events: none;
&--loaded {
visibility: visible;
opacity: 1;
}
}
</style>
What is lazy-loading?
Lazy loading basicly means that the rendering of a element is delayed until it becomes visible. The advantage of this is that your elements are not all loaded at once, but only when needed. This greatly increases the performance of your web-app.
The template
The HTML-part of this component is just an image with a :src
, :class
and a @load
attached to it. The src
is set when the element become visible (more on that in the javascript part). The @load
is fired when the image is done loading and the :class
makes sure the image becomes visible.
The javascript part
This part is where most of the work is done. The intersection observer does most of the work.
In the mounted
hook we register the IntersectionObserver
. This fires the method loadImage
when the element is visible in the viewport. In the loadImage
we check if the element is entering of leaving the viewport by checking the isIntersecting
property of the event which is fired by the intersection observer.
When the element is entering the viewport we set the loadedSrc
to the intended src
(which we passed to the component through a prop). Now the image will start loading and when it's done with this, the isLoaded
-method will fire which sets loaded
to true
.
The CSS part
Initialy we want the image to be hidden, because otherwise you would see an broken image icon and we don't want that. That's why we set opacity:0
and visibility: hidden
. In the template part we set a dynamic class lazy-image--loaded
. This class is only applied when loaded
is true
. That's were you want the image to become visible. So we set opacity: 1
and visibility: visibility
and our image becomes visibile.
And this is how you do lazy-loading in a Vue component.
Usage
This is how you would you inside your project.
<template>
<lazy-image src="https://somecdn.com/assets/myimage.png" />
</template>
<script>
export default {
components: {
LazyImage: () => import('~/components/LazyImage'),
}
}
</script>
Update: 30 may 2022
The browser-support for loading="lazy" is getting better nowadays. This attribute tells the browser to load the resource (iframe or image) when it becomes visible in the viewport. Of course this is much more performant than a custom-built solution.
So you may be better of using this attribute and use a polyfill for browsers who don't support the attribute yet.