Create a Three.js loading component with Nuxt.js
It’s pretty common now to see web experiences using loading components while assets load. Depending on the scene you load, you could multiply textures, 3D models or any other heavy ressource. Three.js ship a prebuilt function that allow you to handle this case.
I’ve discovered this function when trying to load 4 textures on each side of a cube in one of my last project. It caused two problems : either a lag at the page loading when preloading the texture without loader, nor a lag when calling the texture for the first time. It was terrible for user experience. I’ve managed to solve the problem by using a loading component while getting every assets.
Final result : https://inception.etiennemoureton.fr/
The DefaultLoadingManager
It is a three.js package feature that you can import like this :
import { DefaultLoadingManager } from "three";
It is used by default by every three.js loaders, but you can build your own (divide textures, models) by using the LoadingManager.
Then, here is an exemple of usage :
export class Loader {
constructor() {
this.init()
}
init() {
DefaultLoadingManager.onLoad = () => {
console.log( 'Loading Complete!');
};
DefaultLoadingManager.onProgress = ( url, itemsLoaded, itemsTotal ) => {
const percentage = (itemsLoaded / itemsTotal) * 100
const event = new CustomEvent('assetsLoader', {
detail: percentage
}})
document.dispatchEvent(event);
};
}
}
So our init() calls the onProgress method of DefaultLoadingManager
. It takes few parameters :
- url : the current asset being loaded
- itemsLoaded : the amout of assets loaded a the moment
- itemsTotal : the total amount of assets
Then we convert this into a percentage and emit a new DOM CustomEvent taking our percentage as argument.
P.S : It might not be as precise as you wish because the percentage values are only defined by the amount of assets you load.
Ex :
- If you have 4 assets, it will returns in order 25, 50, 75 & 100%
- If you have 5 assets, it will returns in order 20, 40, 60, 80 & 100%
P.P.S : You can build something to smooth the value later from the component.
The loading component
<template>
<div
class="loader"
ref="loader"
:class=[percentage === 100 && 'hide']
>
<p class="loader__title">Wait for assets to load</p>
<div class="loader__progress__container">
<p class="loader__progress__amount">{{ Math.round(percentage) }} %</p>
<div class="loader__progress">
<div :style="'width: ' + percentage + '%'" class="loader__progress__bar"></div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
percentage: 0,
}
},
mounted() {
document.addEventListener('assetsLoader', (e) => {
this.percentage = e.detail.percentage
});
},
}
</script>
On each ‘assetsLoader’ event we create, it updates the component by increasing the bar length & label.
Here is some styles if needed :
.<style lang="scss">
.loader {
position: fixed;
z-index: 3;
width: 100vw;
height: 100vh;
background-color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.2rem;
&__title {
color: black;
margin: 0;
font-size: 1rem;
}
&__progress {
width: 100%;
max-width: 200px;
height: 0.5rem;
background-color: black;
&__amount {
color: black;
width: 30px;
text-align: right;
}
&__container {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
}
&__bar {
height: 100%;
background-color: lightgray;
transition: width 0.1s ease-in;
}
}
}
</style>
Feedbacks are welcomed.