React - 如何在 DOM 还在渲染的时候展示加载动画?

这是一个来自谷歌 Adsense 应用页面的例子。在主界面展示之前加载动画。

enter image description here

我不知道如何用React做同样的事情,因为如果我用React组件渲染加载屏幕,它不会在页面加载时显示,因为它必须等待DOM渲染之前。

更新:

我举了一个例子,把屏幕加载器放在index.html中,并在React componentDidMount()生命周期方法中删除它。

例子react-loading-screen

535404 次浏览

这个问题的解决方法是在render函数中这样做:

constructor() {
this.state = { isLoading: true }
}


componentDidMount() {
this.setState({isLoading: false})
}


render() {
return(
this.state.isLoading ? *showLoadingScreen* : *yourPage()*
)
}

在构造函数中将isLoading初始化为true,在componentDidMount上将其初始化为false。

这可以通过在html文件(例如index.html)中放置加载图标来实现,这样用户就可以在html文件加载后立即看到图标。

当你的应用程序完成加载时,你可以简单地在生命周期钩子中删除加载图标,我通常在componentDidMount中这样做。

当你的React应用程序是巨大的,它真的需要时间来启动和运行后,页面已经加载。例如,你将应用程序的React部分挂载到#app。通常,index.html中的这个元素只是一个空的div:

<div id="app"></div>

你可以做的是放一些样式和一堆图像,让它在页面加载和初始React应用程序渲染到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>

页面加载后,用户将立即看到index.html的原始内容。不久之后,当React准备好将呈现的组件的整个层次结构挂载到这个DOM节点时,用户将看到实际的应用程序。

注意class,而不是className。这是因为你需要把它放到html文件中。


如果你使用SSR,事情就不那么复杂了,因为用户会在页面加载后立即看到真正的应用程序。

我们的目标

当html页面被渲染时,立即显示一个转轮(在React加载时),并在React准备好后隐藏它。

由于旋转器是在纯HTML/CSS中呈现的(在React域之外),React不应该直接控制显示/隐藏过程,实现对React应该是透明的。

解决方案1 -:empty伪类

由于你将react渲染到一个DOM容器- <div id="app"></div>中,你可以向该容器中添加一个旋转器,当react加载并渲染时,旋转器将消失。

你不能在react根中添加DOM元素(例如div),因为一旦调用ReactDOM.render(), react就会替换容器的内容。即使你渲染了null,内容仍然会被注释<!-- react-empty: 1 -->所取代。这意味着如果你想在主组件挂载时显示加载器,数据正在加载,但实际上什么都没有呈现,那么放置在容器内的加载器标记(例如<div id="app"><div class="loader"></div></div>)将不起作用。

一种变通方法是将spinner类添加到react容器中,并使用:empty伪类. spinner类。旋转器将是可见的,只要没有任何东西被呈现到容器中(注释不算数)。一旦react渲染了注释以外的东西,加载器就会消失。

示例1

在这个例子中,你可以看到一个组件呈现null直到它准备好。容器也是加载器——<div id="app" class="app"></div>,而加载器的类只有在它是:empty时才会工作(参见代码中的注释):

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 -->

示例2

使用:empty伪类来显示/隐藏选择器的一种变体是将旋转器设置为应用程序容器的兄弟元素,并使用相邻兄弟组合子 (+)在容器为空时显示它:

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>


解决方案2 -传递旋转器“处理程序”作为道具

要对旋转器显示状态进行更细粒度的控制,可以创建两个函数showSpinnerhideSpinner,并通过props将它们传递给根容器。这些函数可以操作DOM,或者执行控制旋转器所需的任何操作。通过这种方式,React不知道“外部世界”,也不需要直接控制DOM。你可以很容易地替换函数进行测试,或者如果你需要改变逻辑,你可以将它们传递给React树中的其他组件。

示例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>

例2 -钩子

下面的例子使用useEffect钩子在组件挂载后隐藏旋转器。

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>

如果有人正在为上述用例寻找一个插入式、零配置和零依赖的库,请尝试pace.js (https://codebyzach.github.io/pace/docs/)。

它自动挂钩事件(ajax, readyState,历史推送状态,js事件循环等),并显示一个可定制的加载器。

与我们的react/relay项目一起工作得很好(使用react-router, relay请求处理导航更改) (不是affliated;已经使用pace.js为我们的项目,它工作得很好)

我最近不得不处理这个问题,并想出了一个解决方案,这对我来说很好。然而,我已经尝试了上面的@Ori Drori解决方案,不幸的是,它没有正确工作(有一些延迟+我不喜欢setTimeout函数的使用)。

这是我想到的:

index.html文件

内部 head标签-指示器的样式:

<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>

现在 body标签:

<div id="spinner" class="container">
<div class="loading"></div>
</div>


<div id="app"></div>

然后是一个非常简单的逻辑,在app.js文件中(在渲染函数中):

const spinner = document.getElementById('spinner');


if (spinner && !spinner.hasAttribute('hidden')) {
spinner.setAttribute('hidden', 'true');
}

如何它工作吗?

当第一个组件(在我的应用程序中,它在大多数情况下也是app.js)正确安装时,spinner将被隐藏,并对其应用hidden属性。

更重要的是加上- !spinner.hasAttribute('hidden')条件阻止在每个组件挂载时都向旋转器添加hidden属性,因此实际上它只会在整个应用程序加载时添加一次

我使用react-progress-2 npm包,它是零依赖的,在ReactJS中工作得很好。

https://github.com/milworm/react-progress-2

安装:

npm install react-progress-2

在项目中包含react-progress-2/main.css。

import "node_modules/react-progress-2/main.css";

包括react-progress-2并将其放在顶部组件的某个位置,例如:

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>
);
}
});

现在,无论何时你需要显示一个指示器,只需调用Progress.show(),例如:

loadFeed: function() {
Progress.show();
// do your ajax thing.
},


onLoadFeedCallback: function() {
Progress.hide();
// render feed.
}

请注意,showhide调用是堆叠的,所以在n个连续的show调用之后,你需要执行n个hide调用来隐藏一个指示器,或者你可以使用Progress.hideAll()

在componentDidMount中设置超时工作,但在我的应用程序中,我收到了内存泄漏警告。试试这样的方法。

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;
}
}

我也在我的应用程序中使用React。对于请求,我使用axios拦截器,所以让加载器屏幕的好方法(fullpage,因为你展示了一个例子)是在拦截器内部添加类或id为例(这里的代码来自官方文档,一些自定义代码):

// 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);
});

然后在CSS中使用伪元素实现你的加载器(或添加类或id到不同的元素,而不是你喜欢的body) -你可以设置背景的颜色为不透明或透明,等等…例子:

custom-loader:before {
background: #000000;
content: "";
position: fixed;
...
}


custom-loader:after {
background: #000000;
content: "Loading content...";
position: fixed;
color: white;
...
}
公众< em > < / em >文件夹中编辑index.html文件的位置。将图像复制到公共文件夹中与index . html相同的位置。 然后将index.html中包含<div id="root"> </div>标记的部分内容替换为下面给定的html代码
<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>

在加载过程中,Logo现在将出现在页面中间。并将在几秒钟后被React替换。

最重要的问题是:“loading”是什么意思?如果你谈论的是被安装的物理元素,这里的一些第一个答案很好。然而,如果你的应用做的第一件事是检查身份验证,你真正加载的是来自后端的数据,用户是否传递了一个cookie,标签他们是授权或未授权的用户。

这是基于redux的,但你可以很容易地将其更改为普通的反应状态模型。

行动的创造者:

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
});
}
};
};

最后一部分意味着无论用户是否被授权,在收到响应后加载屏幕都会消失。

下面是加载它的组件的样子:

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();
}


}

如果状态。加载是真实的,我们总是会看到加载屏幕。在componentDidMount上,我们调用getTodos函数,它是一个转换状态的动作创建器。当我们得到一个响应(这可能是一个错误)加载假。我们的组件更新,再次调用渲染,这一次没有加载屏幕,因为if语句。

现在我们也可以在React 16.8中使用钩子:

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;

这将发生在ReactDOM.render()控制 <div>之前。也就是说,你的应用程序到那时还没有被安装。

所以你可以在根<div>中的index.html文件中添加加载器。在React接管之前,这将在屏幕上可见。

你可以使用任何最适合你的加载器元素(例如带有动画的svg)。

您不需要在任何生命周期方法上删除它。React将用你渲染的<App/>替换它的 <div>的所有子结点,正如我们在下面的动图中看到的那样。

< a href = " https://codesandbox。io/s/prod-haze-icxdm" rel="noreferrer"> CodeSandbox示例 . io/s/prod-haze-icxdm" rel="noreferrer">

enter image description here

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

ReactDOM.render()运行之前,使用debugger检查页面。

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);
react应用程序的启动是基于主bundle下载的。React应用程序只在浏览器中下载主包后启动。在延迟加载架构中也是如此。 但事实是,我们不能确切地说出任何包的名称。因为当你运行'npm run build'命令时,webpack会在每个bundle的末尾添加一个散列值。当然,我们可以通过改变散列设置来避免这种情况,但这会严重影响浏览器中的缓存数据问题。由于相同的包名,浏览器可能不接受新版本。 我们需要webpack + js + CSS的方法来处理这种情况

更改public/index.html如下所示

<!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>

在你的webpack产品配置中,将HtmlWebpackPlugin选项改为如下

 new HtmlWebpackPlugin({
inject: false,
...

您可能需要使用'eject'命令来获取配置文件。最新的webpack可能有选项配置HtmlWebpackPlugin而不弹出项目。 enter image description here < / p >

你不需要那么多的努力,这里有一个基本的例子。

<!DOCTYPE html>
<html lang="en">


<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Title</title>
<style>
body {
margin: 0;
}


.loader-container {
width: 100vw;
height: 100vh;
display: flex;
overflow: hidden;
}


.loader {
margin: auto;
border: 5px dotted #dadada;
border-top: 5px solid #3498db;
border-radius: 50%;
width: 100px;
height: 100px;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}


@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}


100% {
-webkit-transform: rotate(360deg);
}
}


@keyframes spin {
0% {
transform: rotate(0deg);
}


100% {
transform: rotate(360deg);
}
}


</style>
</head>


<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<div class="loader-container">
<div class="loader"></div>
</div>
</div>
</body>


</html>

你可以使用HTMLCSS来使它看起来像你的例子。

我还使用了@Ori Drori的答案,并设法让它起作用。随着React代码的增长,客户端浏览器在第一次访问时必须下载的编译包也会增加。如果你处理不好,这会带来用户体验问题。

我添加到@Ori回答是添加和执行onload函数在index.html的onload属性的主体标签,以便加载器消失后,一切都已完全加载在浏览,见下面的代码片段:

<html>
<head>
<style>
.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);
}
}
</style>
<script>
function onLoad() {
var loader = document.getElementById("cpay_loader");loader.className = "";}
</script>
</head>
<body onload="onLoad();">
more html here.....
</body>
</html>

那么使用速度呢?

使用这里的链接地址。

https://github.hubspot.com/pace/docs/welcome/

1.在他们的网站上选择你想要的样式并粘贴在index.css中

2.复制Pace Js的链接并添加到public/index.html中的脚本标签中

3.它会自动检测网页负载,并在浏览器顶部显示速度。

你也可以在css中修改高度和动画。

这是我基于答案的实现

/公共/ index . html

<!DOCTYPE html>
<html lang="en">


<head>
<title>React App</title>
<style>
.preloader {
display: flex;
justify-content: center;
}


.rotate {
animation: rotation 1s infinite linear;
}


.loader-hide {
display: none;
}


@keyframes rotation {
from {
transform: rotate(0deg);
}


to {
transform: rotate(359deg);
}
}
</style>
</head>


<body>
<div class="preloader">
<img src="https://i.imgur.com/kDDFvUp.png" class="rotate" width="100" height="100" />
</div>
<div id="root"></div>
</body>


</html>

/ src / app.js

import React, { useEffect } from "react";


import "./App.css";


const loader = document.querySelector(".preloader");


const showLoader = () => loader.classList.remove("preloader");
const addClass = () => loader.classList.add("loader-hide");


const App = () => {
useEffect(() => {
showLoader();
addClass();
}, []);
return (
<div style=\{\{ display: "flex", justifyContent: "center" }}>
<h2>App react</h2>
</div>
);
};


export default App;


你可以通过在react中使用惰性加载轻松做到这一点。 为此,你必须使用lazy和悬疑从react那样
import React, { lazy, Suspense } from 'react';


const loadable = (importFunc, { fallback = null } = { fallback: null }) => {
const LazyComponent = lazy(importFunc);


return props => (
<Suspense fallback={fallback}>
<LazyComponent {...props} />
</Suspense>
);
};


export default loadable;

然后像这样导出你的组件。

export const TeacherTable = loadable(() =>
import ('./MainTables/TeacherTable'), {
fallback: <Loading />,
});

然后在你的路由文件中像这样使用它。

 <Route exact path="/app/view/teachers" component={TeacherTable} />

现在你就可以开始了,每次你的DOM渲染时,你的Loading组件都会显示出来,就像我们在上面的回退属性中指定的那样。只要确保你只在componentDidMount ()中做任何ajax请求

我不知道现在回答是否太晚,因为你可能已经找到了解决方案,但这里有一个来自我的观点,供将来的人参考,因为这个问题真的很有用。:
我从scrimba.com上了一节课,在这里,老师从课堂开始,然后开始上课。他通过课堂和状态来教授API调用。下面是他的代码:

import React, {Component} from "react"


class App extends Component {
constructor() {
super()
this.state = {
loading: false,
character: {}
}
}
    

componentDidMount() {
this.setState({loading: true})
fetch("https://swapi.dev/api/people/1/")
.then(response => response.json())
.then(data => {
this.setState({
loading: false,
character: data
})
})
}
    

render() {
const text = this.state.loading ? "loading..." : this.state.character.name
return (
<div>
<p>{text}</p>
</div>
)
}
}


export default App
所以,这是非常直接的,在开始时将加载状态设置为true并保持它,直到接收到数据,然后当它接收到时,更改状态并将加载设置为false并显示内容。
现在我试着用钩子,作为练习,它工作得很顺利!一个简单而有效的解决方案。这是我的代码:

import React, {useState,useEffect} from 'react'


function App()
{
const [response, setResponse] = useState([]);
    

const [loading, setLoading] = useState(true);


useEffect(() => {
fetchResponse() ;
} , []);


const fetchResponse = async () => {
const data = await fetch("https://swapi.dev/api/people/1/");
const response = await data.json();


setResponse(response);
console.log(response.name);
setLoading(false);
}


const content = loading ? <i className="fas fa-atom fa-spin"></i> : <h1>{response.name}</h1>


return(
<section id="w-d-p">
{content}
</section>
)
}


export default App;

钩子也是一样的逻辑。在数据加载时,我得到了漂亮的转轮然后,我的数据!

哦,顺便说一下,如果你不喜欢这个XD,你可以在fetch中放入自己的API。

这个问题可以通过React的lazy特性轻松解决。

import { Suspense, lazy } from "react"
import Loading from "components/Loading"


const Dashboard = lazy(() => import("containers/Dashboard"))


const App = () => (
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
)


export default App

当仪表板组件仍在加载时,加载组件将显示出来。

如果你使用react-router来管理你的应用程序的路由,你可以很容易地用我做的react-router-loading库添加加载屏幕。

它也会影响页面切换,但我认为,如果你想预装第一页,自然也会预装其他页面。

react-router-loading

此方法与Suspense的区别在于,使用此库,您可以在获取数据时继续加载等等。 基本上,这个方法非常类似于在组件中使用isLoading状态,但是如果你有很多不同的页面,实现起来会容易得多

使用

在路由器部分,从react-router-loading导入SwitchRoute,而不是react-router-dom

import { Switch, Route } from "react-router-loading";


<Switch>
<Route path="/page1" component={Page1} />
<Route path="/page2" component={Page2} />
...
</Switch>

在切换之前必须加载的每个路由中添加loading道具

<Switch>
// data will be loaded before switching
<Route path="/page1" component={Page1} loading />


// instant switch as before
<Route path="/page2" component={Page2} />
...
</Switch>

在带有loading道具的路由中提到的组件的初始加载方法的末尾添加loadingContext.done()(在本例中是Page1)

import { LoadingContext } from "react-router-loading";
const loadingContext = useContext(LoadingContext);


const loading = async () => {
// loading some data


// call method to indicate that loading is done and we are ready to switch
loadingContext.done();
};

你可以指定加载屏幕,将显示在你的应用程序的第一次加载

const MyLoadingScreen = () => <div>Loading...</div>


<Switch loadingScreen={MyLoadingScreen}>
...
</Switch>

只要在<div id="root"></div>标签中添加内容,你就可以开始了!

// Example:


<div id="root">
<div id="pre-loader">
<p>Loading Website...</p>
<img src="/images/my-loader.gif" />
</div>
</div>

一旦<App />被加载,React会自动忽略<div id="root">标签内的所有内容,用你实际的应用程序覆盖它!

来自React文档,

< p >反应。Lazy函数允许将动态导入作为常规导入呈现 组件。< / p > 这将自动加载包含OtherComponent的包 当这个组件第一次被渲染时 < p >反应。Lazy接受一个必须调用动态import()的函数。这 必须返回一个承诺,该承诺解析为一个具有默认导出的模块

lazy组件应该在一个悬疑中呈现 组件,它允许我们显示一些备用内容(例如 当我们等待惰性组件加载时,

import React, { Suspense } from 'react';


const OtherComponent = React.lazy(() => import('./OtherComponent'));


function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
回退道具接受你想渲染的任何React元素 在等待组件加载时。你可以设置悬念 组件位于惰性组件之上。你甚至可以包裹

.

.

Source .