L'objectif
Lorsque la page html est rendue, afficher un spinner immédiatement (pendant le chargement de React), et le cacher une fois que React est prêt.
Puisque le spinner est rendu en HTML/CSS pur (en dehors du domaine React), React ne devrait pas contrôler directement le processus d'affichage et de masquage, et l'implémentation devrait être transparente pour React.
Solution 1 - la pseudo-classe :empty
Puisque vous rendez react dans un conteneur DOM - <div id="app"></div>
vous pouvez ajouter un spinner à ce conteneur, et lorsque react se charge et effectue le rendu, le spinner disparaît.
Vous ne pouvez pas ajouter un élément DOM (un div par exemple) à l'intérieur de react Root, car React remplacera le contenu du conteneur dès que ReactDOM.render()
est appelé. Même si vous rendez null
le contenu serait quand même remplacé par un commentaire. <!-- react-empty: 1 -->
. Cela signifie que si vous souhaitez afficher le chargeur pendant que le composant principal se monte, que les données se chargent, mais que rien n'est réellement rendu, une balise de chargeur placée à l'intérieur du conteneur ( <div id="app"><div class="loader"></div></div>
par exemple) ne fonctionnerait pas.
Une solution de contournement consiste à ajouter la classe spinner au conteneur react, et à utiliser l'attribut :empty
pseudo classe . Le spinner sera visible, tant que rien n'est rendu dans le conteneur (les commentaires ne comptent pas). Dès que react rendra autre chose qu'un commentaire, le chargeur disparaîtra.
Exemple 1
Dans l'exemple, vous pouvez voir un composant qui restitue null
jusqu'à ce qu'il soit prêt. Le conteneur est aussi le chargeur - <div id="app" class="app"></div>
et la classe du chargeur ne fonctionnera que si elle est :empty
(voir les commentaires dans le code) :
class App extends React.Component {
state = {
loading: true
};
componentDidMount() {
// this simulates an async action, after which the component will render the content
demoAsyncCall().then(() => this.setState({ loading: false }));
}
render() {
const { loading } = this.state;
if(loading) { // if your component doesn't have to wait for an async action, remove this block
return null; // render null when app is not ready
}
return (
<div>I'm the app</div>
);
}
}
function demoAsyncCall() {
return new Promise((resolve) => setTimeout(() => resolve(), 2500));
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
.loader:empty {
position: absolute;
top: calc(50% - 4em);
left: calc(50% - 4em);
width: 6em;
height: 6em;
border: 1.1em solid rgba(0, 0, 0, 0.2);
border-left: 1.1em solid #000000;
border-radius: 50%;
animation: load8 1.1s infinite linear;
}
@keyframes load8 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>
<div id="app" class="loader"></div> <!-- add class loader to container -->
Exemple 2
Une variante de l'utilisation de la :empty
pour afficher/masquer un sélecteur, c'est de faire du spinner un élément frère du conteneur de l'application et de l'afficher tant que le conteneur est vide à l'aide de la pseudo-classe Combinateur de frères et sœurs adjacents ( +
) :
class App extends React.Component {
state = {
loading: true
};
componentDidMount() {
// this simulates an async action, after which the component will render the content
demoAsyncCall().then(() => this.setState({ loading: false }));
}
render() {
const { loading } = this.state;
if(loading) { // if your component doesn't have to wait for async data, remove this block
return null; // render null when app is not ready
}
return (
<div>I'm the app</div>
);
}
}
function demoAsyncCall() {
return new Promise((resolve) => setTimeout(() => resolve(), 2500));
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
#app:not(:empty) + .sk-cube-grid {
display: none;
}
.sk-cube-grid {
width: 40px;
height: 40px;
margin: 100px auto;
}
.sk-cube-grid .sk-cube {
width: 33%;
height: 33%;
background-color: #333;
float: left;
animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
}
.sk-cube-grid .sk-cube1 {
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube2 {
animation-delay: 0.3s;
}
.sk-cube-grid .sk-cube3 {
animation-delay: 0.4s;
}
.sk-cube-grid .sk-cube4 {
animation-delay: 0.1s;
}
.sk-cube-grid .sk-cube5 {
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube6 {
animation-delay: 0.3s;
}
.sk-cube-grid .sk-cube7 {
animation-delay: 0s;
}
.sk-cube-grid .sk-cube8 {
animation-delay: 0.1s;
}
.sk-cube-grid .sk-cube9 {
animation-delay: 0.2s;
}
@keyframes sk-cubeGridScaleDelay {
0%,
70%,
100% {
transform: scale3D(1, 1, 1);
}
35% {
transform: scale3D(0, 0, 1);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>
<div id="app"></div>
<!-- add class loader to container -->
<div class="sk-cube-grid">
<div class="sk-cube sk-cube1"></div>
<div class="sk-cube sk-cube2"></div>
<div class="sk-cube sk-cube3"></div>
<div class="sk-cube sk-cube4"></div>
<div class="sk-cube sk-cube5"></div>
<div class="sk-cube sk-cube6"></div>
<div class="sk-cube sk-cube7"></div>
<div class="sk-cube sk-cube8"></div>
<div class="sk-cube sk-cube9"></div>
</div>
Solution 2 - Passer les "handlers" de spinner comme props
Pour avoir un contrôle plus fin sur l'état d'affichage des spinners, créez deux fonctions showSpinner
et hideSpinner
et les transmettre au conteneur Root via les props. Les fonctions peuvent manipuler le DOM, ou faire tout ce qui est nécessaire pour contrôler le spinner. De cette façon, React n'est pas conscient du "monde extérieur" et n'a pas besoin de contrôler le DOM directement. Vous pouvez facilement remplacer les fonctions pour les tests, ou si vous devez changer la logique, et vous pouvez les passer à d'autres composants dans l'arbre React.
Exemple 1
const loader = document.querySelector('.loader');
// if you want to show the loader when React loads data again
const showLoader = () => loader.classList.remove('loader--hide');
const hideLoader = () => loader.classList.add('loader--hide');
class App extends React.Component {
componentDidMount() {
this.props.hideLoader();
}
render() {
return (
<div>I'm the app</div>
);
}
}
// the setTimeout simulates the time it takes react to load, and is not part of the solution
setTimeout(() =>
// the show/hide functions are passed as props
ReactDOM.render(
<App
hideLoader={hideLoader}
showLoader={showLoader}
/>,
document.getElementById('app')
)
, 1000);
.loader {
position: absolute;
top: calc(50% - 4em);
left: calc(50% - 4em);
width: 6em;
height: 6em;
border: 1.1em solid rgba(0, 0, 0, 0.2);
border-left: 1.1em solid #000000;
border-radius: 50%;
animation: load8 1.1s infinite linear;
transition: opacity 0.3s;
}
.loader--hide {
opacity: 0;
}
@keyframes load8 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>
<div id="app"></div>
<div class="loader"></div>
Exemple 2 - crochets
Cet exemple utilise le useEffect
pour masquer le spinner après le montage du composant.
const { useEffect } = React;
const loader = document.querySelector('.loader');
// if you want to show the loader when React loads data again
const showLoader = () => loader.classList.remove('loader--hide');
const hideLoader = () => loader.classList.add('loader--hide');
const App = ({ hideLoader }) => {
useEffect(hideLoader, []);
return (
<div>I'm the app</div>
);
}
// the setTimeout simulates the time it takes react to load, and is not part of the solution
setTimeout(() =>
// the show/hide functions are passed as props
ReactDOM.render(
<App
hideLoader={hideLoader}
showLoader={showLoader}
/>,
document.getElementById('app')
)
, 1000);
.loader {
position: absolute;
top: calc(50% - 4em);
left: calc(50% - 4em);
width: 6em;
height: 6em;
border: 1.1em solid rgba(0, 0, 0, 0.2);
border-left: 1.1em solid #000000;
border-radius: 50%;
animation: load8 1.1s infinite linear;
transition: opacity 0.3s;
}
.loader--hide {
opacity: 0;
}
@keyframes load8 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="app"></div>
<div class="loader"></div>
1 votes
Montrez ce que vous voulez montrer en js simple, puis rendez-le caché ou supprimez-le du DOM lorsque la réaction est montée. Tout ce que vous avez à faire est de le cacher du code de react.
2 votes
C'est tout simplement merveilleux ! Merci.
2 votes
Je suis d'accord pour dire que c'est une approche merveilleuse. J'ai expédié plusieurs applications react où j'ai mis l'écran de chargement à l'intérieur de <div id="Root"> </div> (ce qui fonctionne) mais il peut y avoir un "écran blanc" de courte durée entre le premier appel de ReactDOM.render() et le moment où le composant serait effectivement peint. L'utilisation d'un positionnement fixe pour l'écran de chargement, puis du crochet componentDidUpdate (ou useEffect) avec CSS pour l'estomper puis le supprimer complètement est merveilleuse. Cela garantit que vous ne supprimez pas l'écran de chargement avant que votre composant react entièrement peint ne soit déjà en dessous, prêt à être visualisé.