reactjs - react - Reaccionar: ¿Mostrar la pantalla de carga mientras se procesa DOM?
react js ejemplos (14)
La meta
Cuando se procesa la página html, muestre una rueda giratoria inmediatamente (mientras React carga), y escóndela después de que React esté listo.
Dado que la ruleta se representa en HTML / CSS puro (fuera del dominio React), React no debe controlar el proceso de mostrar / ocultar directamente, y la implementación debe ser transparente para React.
Solución 1 - la: pseudo-clase vacía
Dado que el render reacciona en un contenedor DOM -
<div id="app"></div>
, puede agregar una ruleta a ese contenedor, y cuando reaccione se cargará y procesará, la ruleta desaparecerá.
No puede agregar un elemento DOM (un div por ejemplo) dentro de la raíz de reacción, ya que React reemplazará el contenido del contenedor tan pronto como se
ReactDOM.render()
.
Incluso si se vuelve
null
, el contenido aún se reemplazaría por un comentario -
<!-- react-empty: 1 -->
.
Esto significa que si desea mostrar el cargador mientras se monta el componente principal, los datos se están cargando, pero en realidad no se representa nada, se coloca un marcado del cargador dentro del contenedor (
<div id="app"><div class="loader"></div></div>
por ejemplo) no funcionaría.
Una solución alternativa es agregar la clase spinner al contenedor de reacción y usar la
pseudo clase
:empty
.
La rueda giratoria será visible, siempre que no se muestre nada en el contenedor (los comentarios no cuentan).
Tan pronto como reacciona representa algo más que comentario, el cargador desaparecerá.
Ejemplo 1
En el ejemplo, puede ver un componente que se vuelve
null
hasta que esté listo.
El contenedor también es el cargador:
<div id="app" class="app"></div>
, y la clase del cargador solo funcionará si está
:empty
(ver comentarios en el código):
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 -->
Ejemplo 2
Una variación sobre el uso de
:empty
pseudo clase
:empty
para mostrar / ocultar un selector, es configurar la ruleta como un elemento hermano para el contenedor de la aplicación, y mostrarla siempre que el contenedor esté vacío usando el
combinador de hermanos adyacente
(
+
):
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>
Solución 2 - Pase los "controladores" de la ruleta como accesorios
Para tener un control más detallado sobre el estado de visualización de los hilanderos, cree dos funciones
showSpinner
y
hideSpinner
, y
hideSpinner
al contenedor raíz mediante accesorios.
Las funciones pueden manipular el DOM o hacer lo que sea necesario para controlar la ruleta.
De esta manera, React no es consciente del "mundo exterior", ni necesita controlar el DOM directamente.
Puede reemplazar fácilmente las funciones para la prueba, o si necesita cambiar la lógica, y puede pasarlas a otros componentes en el árbol React.
Ejemplo 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>
Ejemplo 2 - ganchos
Este ejemplo utiliza el gancho
useEffect
para ocultar la ruleta después de que se monte el componente.
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>
Este es un ejemplo de la página de la aplicación Google Adsense. La pantalla de carga mostrada antes de la página principal mostrada después.
No sé cómo hacer lo mismo con React porque si hago que la pantalla de carga sea procesada por el componente React, no se muestra mientras se carga la página porque tiene que esperar a que DOM se procese antes.
Actualizado :
Hice un ejemplo de mi enfoque colocando el cargador de pantalla en
index.html
y eliminándolo en el método de ciclo de vida React
componentDidMount()
.
Ejemplo: https://nguyenbathanh.github.io
Fuente: https://github.com/nguyenbathanh/react-loading-screen
Cuando su aplicación React es masiva, realmente toma tiempo para que se ponga en funcionamiento después de que se haya cargado la página.
Digamos que
#app
tu parte React de la aplicación en
#app
.
Por lo general, este elemento en su index.html es simplemente un div vacío:
<div id="app"></div>
Lo que puedes hacer en su lugar es poner un poco de estilo y un montón de imágenes allí para que se vea mejor entre la carga de la página y la representación inicial de la aplicación React a DOM:
<div id="app">
<div class="logo">
<img src="/my/cool/examplelogo.svg" />
</div>
<div class="preload-title">
Hold on, it''s loading!
</div>
</div>
Después de cargar la página, el usuario verá inmediatamente el contenido original de index.html. Poco después, cuando React esté listo para montar toda la jerarquía de componentes renderizados en este nodo DOM, el usuario verá la aplicación real.
Nota
class
, no
className
.
Es porque necesitas poner esto en tu archivo html.
Si usa SSR, las cosas son menos complicadas porque el usuario realmente verá la aplicación real justo después de que se cargue la página.
Edite la ubicación de su archivo index.html en la carpeta
pública
.
Copie su imagen en la misma ubicación que
index.html
en la carpeta pública.
Y luego reemplace la parte del contenido de index.html que contiene las
<div id="root"> </div>
por el siguiente código html dado.
import React, { useState, useEffect } from ''react'';
const app = () => {
const [ spinner, setSpinner ] = useState(true);
// It will be executed before rendering
useEffect(() => {
setTimeout(() => setSpinner(false), 1000)
}, []);
// [] means like componentDidMount
return !spinner && <div >Your content</div>;
}
export default app;
El logotipo ahora aparecerá en el medio de la página durante el proceso de carga. Y luego será reemplazado después de unos segundos por React.
El inicio de la aplicación Reaccionar se basa en la descarga del paquete principal. La aplicación React solo comienza después de que el paquete principal se descarga en el navegador. Esto es incluso cierto en el caso de la arquitectura de carga diferida. Pero el hecho es que no podemos decir exactamente el nombre de ningún paquete. Debido a que webapck agregará un valor hash al final de cada paquete en el momento en que ejecute el comando ''npm run build''. Por supuesto, podemos evitar eso cambiando la configuración de hash, pero afectará seriamente el problema de los datos de caché en el navegador. Es posible que los navegadores no tomen la nueva versión debido al mismo nombre de paquete. . Necesitamos un enfoque webpack + js + CSS para manejar esta situación.
cambie public / index.html como se muestra a continuación
export const getTodos = () => {
return async dispatch => {
let res;
try {
res = await axios.get(''/todos/get'');
dispatch({
type: AUTH,
auth: true
});
dispatch({
type: GET_TODOS,
todos: res.data.todos
});
} catch (e) {
} finally {
dispatch({
type: LOADING,
loading: false
});
}
};
};
En su configuración de producción de webapack, cambie la opción HtmlWebpackPlugin a continuación
class App extends Component {
renderLayout() {
const {
loading,
auth,
username,
error,
handleSidebarClick,
handleCloseModal
} = this.props;
if (loading) {
return <Loading />;
}
return (
...
);
}
...
componentDidMount() {
this.props.getTodos();
}
...
render() {
return this.renderLayout();
}
}
Es posible que necesite usar el comando ''expulsar'' para obtener el archivo de configuración. El último paquete web podría tener la opción de configurar HtmlWebpackPlugin sin expulsar el proyecto.
Establecer el tiempo de espera en componentDidMount funciona pero en mi aplicación recibí una advertencia de pérdida de memoria. Intenta algo como esto.
constructor(props) {
super(props)
this.state = {
loading: true,
}
}
componentDidMount() {
this.timerHandle = setTimeout(() => this.setState({ loading: false }), 3500);
}
componentWillUnmount(){
if (this.timerHandle) {
clearTimeout(this.timerHandle);
this.timerHandle = 0;
}
}
Esto se puede hacer colocando el ícono de carga en su archivo html (index.html para ej.), De modo que los usuarios vean el ícono inmediatamente después de cargar el archivo html.
Cuando su aplicación termina de cargarse, simplemente puede eliminar ese ícono de carga en un enlace de ciclo de vida, generalmente lo hago en
componentDidMount
.
Esto sucederá antes de que
ReactDOM.render()
tome el control de la
raíz
<div>
.
Es decir, su aplicación no se habrá montado hasta ese punto.
Entonces puede agregar su cargador en su archivo
index.html
dentro de la raíz
<div>
.
Y eso será visible en la pantalla hasta que React se haga cargo.
Puede usar cualquier elemento del cargador que funcione mejor para usted (
svg
con animación, por ejemplo).
No es necesario eliminarlo en ningún método de ciclo de vida.
React reemplazará a cualquier elemento secundario de su
raíz
<div>
con su
<App/>
renderizada, como podemos ver en el GIF a continuación.
index.html
<head>
<style>
.svgLoader {
animation: spin 0.5s linear infinite;
margin: auto;
}
.divLoader {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div id="root">
<div class="divLoader">
<svg class="svgLoader" viewBox="0 0 1024 1024" width="10em" height="10em">
<path fill="lightblue"
d="PATH FOR THE LOADER ICON"
/>
</svg>
</div>
</div>
</body>
index.js
Usar el
debugger
para inspeccionar la página antes de que se
ReactDOM.render()
.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
debugger; // TO INSPECT THE PAGE BEFORE 1ST RENDER
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Estoy usando el paquete react-progress-2 npm, que es de dependencia cero y funciona muy bien en ReactJS.
https://github.com/milworm/react-progress-2
Instalación:
npm install react-progress-2
Incluya react-progress-2 / main.css a su proyecto.
import "node_modules/react-progress-2/main.css";
Incluya
react-progress-2
y colóquelo en algún lugar del componente superior, por ejemplo:
import React from "react";
import Progress from "react-progress-2";
var Layout = React.createClass({
render: function() {
return (
<div className="layout">
<Progress.Component/>
{/* other components go here*/}
</div>
);
}
});
Ahora, cada vez que necesite mostrar un indicador, simplemente llame a
Progress.show()
, por ejemplo:
loadFeed: function() {
Progress.show();
// do your ajax thing.
},
onLoadFeedCallback: function() {
Progress.hide();
// render feed.
}
Tenga en cuenta que las llamadas
show
y
hide
están apiladas, por lo que después de mostrar llamadas n consecutivas, debe hacer n ocultar llamadas para ocultar un indicador o puede usar
Progress.hideAll()
.
Hoy en día también podemos usar ganchos en React 16.8:
<div id="root"> <img src="logo-dark300w.png" alt="Spideren" style="vertical-align: middle; position: absolute;
top: 50%;
left: 50%;
margin-top: -100px; /* Half the height */
margin-left: -250px; /* Half the width */" /> </div>
La pregunta más importante es: ¿qué quiere decir con "carga"? Si está hablando del elemento físico que se está montando, algunas de las primeras respuestas aquí son geniales. Sin embargo, si lo primero que hace su aplicación es verificar la autenticación, lo que realmente está cargando son datos del backend, ya sea que el usuario haya pasado una cookie que los etiqueta como usuarios autorizados o no autorizados.
Esto se basa en redux, pero puede cambiarlo fácilmente al modelo de estado de reacción simple.
creador de acción:
<!DOCTYPE html>
<html lang="en" xml:lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=3.0, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<style>
.percentage {
position: absolute;
top: 50%;
left: 50%;
width: 150px;
height: 150px;
border: 1px solid #ccc;
background-color: #f3f3f3;
-webkit-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
border: 1.1em solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
overflow: hidden;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.innerpercentage {
font-size: 20px;
}
</style>
<script>
function showPercentage(value) {
document.getElementById(''percentage'').innerHTML = (value * 100).toFixed() + "%";
}
var req = new XMLHttpRequest();
req.addEventListener("progress", function (event) {
if (event.lengthComputable) {
var percentComplete = event.loaded / event.total;
showPercentage(percentComplete)
// ...
} else {
document.getElementById(''percentage'').innerHTML = "Loading..";
}
}, false);
// load responseText into a new script element
req.addEventListener("load", function (event) {
var e = event.target;
var s = document.createElement("script");
s.innerHTML = e.responseText;
document.documentElement.appendChild(s);
document.getElementById(''parentDiv'').style.display = ''none'';
}, false);
var bundleName = "<%= htmlWebpackPlugin.files.chunks.main.entry %>";
req.open("GET", bundleName);
req.send();
</script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>App Name</title>
<link href="<%= htmlWebpackPlugin.files.chunks.main.css[0] %>" rel="stylesheet">
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="parentDiv" class="percentage">
<div id="percentage" class="innerpercentage">loading</div>
</div>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
La parte final significa si el usuario está autenticado o no, la pantalla de carga desaparece después de recibir una respuesta.
Así es como se vería un componente que lo carga:
new HtmlWebpackPlugin({
inject: false,
...
Si state.loading es verdadero, siempre veremos una pantalla de carga. En componentDidMount, llamamos a nuestra función getTodos, que es un creador de acciones que convierte el estado en carga falsa cuando recibimos una respuesta (que puede ser un error). Nuestros componentes se actualizan, las llamadas se procesan nuevamente, y esta vez no hay pantalla de carga debido a la declaración if.
La solución para esto es:
En su función de render, haga algo como esto:
constructor() {
this.state = { isLoading: true }
}
componentDidMount() {
this.setState({isLoading: false})
}
render() {
return(
this.state.isLoading ? *showLoadingScreen* : *yourPage()*
)
}
Inicializar isLoading como verdadero en el constructor y falso en componentDidMount
Si alguien busca una biblioteca de acceso directo, configuración cero y dependencias cero para el caso de uso anterior, intente pace.js ( http://github.hubspot.com/pace/docs/welcome/ ).
Se conecta automáticamente a eventos (ajax, readyState, historial de estado, bucle de eventos js, etc.) y muestra un cargador personalizable.
Funcionó bien con nuestros proyectos de reacción / retransmisión (maneja los cambios de navegación usando react-router, solicitudes de retransmisión) (No afiliado; había usado pace.js para nuestros proyectos y funcionó muy bien)
También estoy usando React en mi aplicación. Para las solicitudes que estoy usando interceptores axios, una excelente manera de hacer la pantalla del cargador (página completa como mostraste un ejemplo) es agregar clase o id a, por ejemplo, el cuerpo dentro de los interceptores (aquí el código de la documentación oficial con algún código personalizado):
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
document.body.classList.add(''custom-loader'');
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
document.body.classList.remove(''custom-loader'');
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
Y luego simplemente implemente en CSS su cargador con pseudo-elementos (o agregue clase o identificación a un elemento diferente, no el cuerpo como desee): puede establecer el color de fondo en opaco o transparente, etc. Ejemplo:
custom-loader:before {
background: #000000;
content: "";
position: fixed;
...
}
custom-loader:after {
background: #000000;
content: "Loading content...";
position: fixed;
color: white;
...
}
Tuve que lidiar con ese problema recientemente y se me ocurrió una solución, que funciona bien para mí.
Sin embargo, probé la solución @Ori Drori anterior y desafortunadamente no funcionó correctamente (tuve algunos retrasos + No me gusta el uso de la función
setTimeout
allí).
Esto es lo que se me ocurrió:
archivo
index.html
Etiqueta
interior de la
head
: estilos para el indicador:
<style media="screen" type="text/css">
.loading {
-webkit-animation: sk-scaleout 1.0s infinite ease-in-out;
animation: sk-scaleout 1.0s infinite ease-in-out;
background-color: black;
border-radius: 100%;
height: 6em;
width: 6em;
}
.container {
align-items: center;
background-color: white;
display: flex;
height: 100vh;
justify-content: center;
width: 100vw;
}
@keyframes sk-scaleout {
0% {
-webkit-transform: scale(0);
transform: scale(0);
}
100% {
-webkit-transform: scale(1.0);
opacity: 0;
transform: scale(1.0);
}
}
</style>
Ahora
la etiqueta del
body
:
<div id="spinner" class="container">
<div class="loading"></div>
</div>
<div id="app"></div>
Y luego
viene una lógica muy simple, dentro del archivo
app.js
(en la función de renderizado):
const spinner = document.getElementById(''spinner'');
if (spinner && !spinner.hasAttribute(''hidden'')) {
spinner.setAttribute(''hidden'', ''true'');
}
Como funciona
Cuando el primer componente (en mi aplicación, también es
app.js
en la mayoría de los casos) se monta correctamente, la
spinner
se oculta al aplicarle
hidden
atributo
hidden
.
Lo que es más importante agregar: la
!spinner.hasAttribute(''hidden'')
impide agregar
hidden
atributo
hidden
a la ruleta con cada montaje de componentes, por lo que en realidad se agregará solo una vez, cuando se cargue toda la aplicación.