React - 在 DOM 渲染时显示加载屏幕?
React - Display loading screen while DOM is rendering?
这是来自 Google Adsense 申请页面的示例。主页面之前显示的加载屏幕显示之后。
我不知道如何用 React 做同样的事情,因为如果我让 React 组件渲染加载屏幕,它不会在页面加载时显示,因为它必须等待 DOM 渲染之前。
已更新:
我通过将屏幕加载器放入 index.html
并在 React componentDidMount()
生命周期方法中将其删除来作为我的方法的示例。
解决方法是:
在你的渲染函数中做这样的事情:
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 不应直接控制 showing/hiding 进程,并且实现对 React 应该是透明的。
解决方案 1 - :empty 伪class
由于您将 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>
)将不起作用。
解决方法是将微调器 class 添加到 React 容器,并使用 :empty
pseudo class。只要容器中没有渲染任何内容,微调器就会可见(注释不算数)。只要 React 渲染了评论以外的东西,加载器就会消失。
示例 1
在示例中,您可以看到呈现 null
直到准备就绪的组件。容器也是加载器 - <div id="app" class="app"></div>
,加载器的 class 只有在 :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 到 show/hide 选择器的一种变体是将微调器设置为应用程序容器的同级元素,并在使用 adjacent sibling combinator (+
):
容器为空
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 - 将微调器 "handlers" 作为道具传递
要对微调器显示状态进行更细粒度的控制,请创建两个函数 showSpinner
和 hideSpinner
,并通过 props 将它们传递给根容器。这些函数可以操纵 DOM,或者做任何需要控制微调器的事情。这样,React 就不会感知 "outside world",也不需要直接控制 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、中继请求处理导航更改)
(无关联;在我们的项目中使用了 pace.js,效果很好)
我最近不得不处理这个问题,并想出了一个解决方案,对我来说效果很好。但是,我已经尝试了上面的@Ori Drori 解决方案,不幸的是它并没有正常工作(有一些延迟 + 我不喜欢那里使用 setTimeout
函数)。
这是我想出的:
index.html
文件
Inside 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.
}
请注意,show
和 hide
调用是堆叠的,因此在连续 n 次显示调用后,您需要执行 n 次隐藏调用以隐藏指示器,或者您可以使用 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 拦截器的请求,制作加载器屏幕的好方法(如您展示的示例所示的整页)是将 class 或 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 中使用伪元素实现您的加载器(或将 class 或 id 添加到不同的元素,而不是您喜欢的正文)- 您可以将背景颜色设置为不透明或透明等...示例:
custom-loader:before {
background: #000000;
content: "";
position: fixed;
...
}
custom-loader:after {
background: #000000;
content: "Loading content...";
position: fixed;
color: white;
...
}
编辑 index.html 文件在 public 文件夹中的位置。将图像复制到与 public 文件夹中 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>
徽标现在会在加载过程中出现在页面中间。然后将在几秒钟后被 React 替换。
最重要的问题是:'loading'是什么意思?如果您正在谈论正在安装的物理元素,那么这里的一些第一个答案很好。但是,如果您的应用程序做的第一件事是检查身份验证,那么您真正加载的是来自后端的数据,无论用户是否传递了将他们标记为授权用户或未授权用户的 cookie。
这是基于 redux,但您可以轻松地将其更改为普通的 React 状态模型。
动作创作者:
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();
}
}
如果 state.loading 为真,我们将始终看到加载屏幕。在 componentDidMount 上,我们调用我们的 getTodos 函数,它是一个动作创建器,当我们得到响应(这可能是一个错误)时,它会变成 state.loading falsy。我们的组件更新,再次调用渲染,这次因为 if 语句没有加载屏幕。
现在我们也可以在 React 16.8 中使用 hooks:
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()
控制 root <div>
之前发生。 IE。到那时你的应用程序还没有安装。
因此您可以将加载程序添加到根 <div>
内的 index.html
文件中。在 React 接管之前,这将在屏幕上可见。
您可以使用任何最适合您的加载器元素(例如 svg
动画)。
您不需要在任何生命周期方法中删除它。 React 将用您渲染的 <App/>
替换其 root <div>
的所有子项,正如我们在下面的 GIF 中看到的那样。
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 app的启动是基于主包下载的。 React 应用仅在浏览器中下载主包后启动。在延迟加载架构的情况下甚至如此。
但事实是我们无法准确说明任何捆绑包的名称。因为 webpack 会在你 运行 'npm run build' 命令时在每个 bundle 的末尾添加一个哈希值。当然我们可以通过更改hash设置来避免,但是会严重影响Browser中的缓存数据问题。由于相同的包名称,浏览器可能不会采用新版本。 .
我们需要一个 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。
你不需要那么多努力,这里有一个基本的例子。
<!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>
您可以尝试使用 HTML
和 CSS
使其看起来像您的示例。
我还使用了@Ori Drori 的回答并设法让它发挥作用。随着 React 代码的增长,客户端浏览器必须在首次访问时下载的编译包也会增长。如果你处理不好,这会带来用户体验问题。
我在@Ori answer中添加的是在body标签的index.html onload属性中添加并执行onload函数,这样在浏览器全部加载完成后loader就消失了,请参阅下面的代码段:
<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>
使用 Pace 怎么样
在此处使用此 link 地址。
https://github.hubspot.com/pace/docs/welcome/
1.On他们的网站select你想要的样式然后粘贴在index.css
2.go 到 cdnjs 复制 Pace Js 的 link 并添加到 public/index.html
中的脚本标签
3.It 自动检测网络负载并在浏览器顶部显示速度。
您还可以在css中修改高度和动画。
这是我的实现,基于答案
./public/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 中使用延迟加载轻松地做到这一点。
为此你必须使用懒惰和悬念来做出这样的反应。
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 呈现时开始了 您的加载组件将按照我们在 fallback 属性 中指定的方式显示多于。只需确保您仅在 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,你可以把你自己的 API 放在 fetch 中。
这个问题可以通过React的惰性特性轻松解决
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 库轻松添加加载屏幕。
也会影响页面切换,不过我觉得如果你想预加载第一页,自然也预加载其他页面。
此方法与 Suspense
的区别在于,使用此库,您可以在获取数据等的同时继续加载。
基本上,此方法与在组件内使用 isLoading
状态非常相似,但如果您有很多不同的页面,则更容易实现。
用法
在您的路由器部分从 react-router-loading
导入 Switch
和 Route
而不是 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
prop 的路由中提到的组件中的初始加载方法末尾添加 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 文档,source。
React.lazy function lets you render a dynamic import as a regular
component.
This will automatically load the bundle containing the OtherComponent
when this component is first rendered.
React.lazy takes a function that must call a dynamic import(). This
must return a Promise which resolves to a module with a default export
containing a React component.
The lazy component should then be rendered inside a Suspense
component, which allows us to show some fallback content (such as a
loading indicator) while we’re waiting for the lazy component to load.
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
The fallback prop accepts any React elements that you want to render
while waiting for the component to load. You can place the Suspense
component anywhere above the lazy component. You can even wrap
multiple lazy components with a single Suspense component.
这是来自 Google Adsense 申请页面的示例。主页面之前显示的加载屏幕显示之后。
我不知道如何用 React 做同样的事情,因为如果我让 React 组件渲染加载屏幕,它不会在页面加载时显示,因为它必须等待 DOM 渲染之前。
已更新:
我通过将屏幕加载器放入 index.html
并在 React componentDidMount()
生命周期方法中将其删除来作为我的方法的示例。
解决方法是:
在你的渲染函数中做这样的事情:
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 不应直接控制 showing/hiding 进程,并且实现对 React 应该是透明的。
解决方案 1 - :empty 伪class
由于您将 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>
)将不起作用。
解决方法是将微调器 class 添加到 React 容器,并使用 :empty
pseudo class。只要容器中没有渲染任何内容,微调器就会可见(注释不算数)。只要 React 渲染了评论以外的东西,加载器就会消失。
示例 1
在示例中,您可以看到呈现 null
直到准备就绪的组件。容器也是加载器 - <div id="app" class="app"></div>
,加载器的 class 只有在 :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 到 show/hide 选择器的一种变体是将微调器设置为应用程序容器的同级元素,并在使用 adjacent sibling combinator (+
):
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 - 将微调器 "handlers" 作为道具传递
要对微调器显示状态进行更细粒度的控制,请创建两个函数 showSpinner
和 hideSpinner
,并通过 props 将它们传递给根容器。这些函数可以操纵 DOM,或者做任何需要控制微调器的事情。这样,React 就不会感知 "outside world",也不需要直接控制 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、中继请求处理导航更改) (无关联;在我们的项目中使用了 pace.js,效果很好)
我最近不得不处理这个问题,并想出了一个解决方案,对我来说效果很好。但是,我已经尝试了上面的@Ori Drori 解决方案,不幸的是它并没有正常工作(有一些延迟 + 我不喜欢那里使用 setTimeout
函数)。
这是我想出的:
index.html
文件
Inside 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.
}
请注意,show
和 hide
调用是堆叠的,因此在连续 n 次显示调用后,您需要执行 n 次隐藏调用以隐藏指示器,或者您可以使用 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 拦截器的请求,制作加载器屏幕的好方法(如您展示的示例所示的整页)是将 class 或 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 中使用伪元素实现您的加载器(或将 class 或 id 添加到不同的元素,而不是您喜欢的正文)- 您可以将背景颜色设置为不透明或透明等...示例:
custom-loader:before {
background: #000000;
content: "";
position: fixed;
...
}
custom-loader:after {
background: #000000;
content: "Loading content...";
position: fixed;
color: white;
...
}
编辑 index.html 文件在 public 文件夹中的位置。将图像复制到与 public 文件夹中 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>
徽标现在会在加载过程中出现在页面中间。然后将在几秒钟后被 React 替换。
最重要的问题是:'loading'是什么意思?如果您正在谈论正在安装的物理元素,那么这里的一些第一个答案很好。但是,如果您的应用程序做的第一件事是检查身份验证,那么您真正加载的是来自后端的数据,无论用户是否传递了将他们标记为授权用户或未授权用户的 cookie。
这是基于 redux,但您可以轻松地将其更改为普通的 React 状态模型。
动作创作者:
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();
}
}
如果 state.loading 为真,我们将始终看到加载屏幕。在 componentDidMount 上,我们调用我们的 getTodos 函数,它是一个动作创建器,当我们得到响应(这可能是一个错误)时,它会变成 state.loading falsy。我们的组件更新,再次调用渲染,这次因为 if 语句没有加载屏幕。
现在我们也可以在 React 16.8 中使用 hooks:
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()
控制 root <div>
之前发生。 IE。到那时你的应用程序还没有安装。
因此您可以将加载程序添加到根 <div>
内的 index.html
文件中。在 React 接管之前,这将在屏幕上可见。
您可以使用任何最适合您的加载器元素(例如 svg
动画)。
您不需要在任何生命周期方法中删除它。 React 将用您渲染的 <App/>
替换其 root <div>
的所有子项,正如我们在下面的 GIF 中看到的那样。
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 app的启动是基于主包下载的。 React 应用仅在浏览器中下载主包后启动。在延迟加载架构的情况下甚至如此。 但事实是我们无法准确说明任何捆绑包的名称。因为 webpack 会在你 运行 'npm run build' 命令时在每个 bundle 的末尾添加一个哈希值。当然我们可以通过更改hash设置来避免,但是会严重影响Browser中的缓存数据问题。由于相同的包名称,浏览器可能不会采用新版本。 . 我们需要一个 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。
你不需要那么多努力,这里有一个基本的例子。
<!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>
您可以尝试使用 HTML
和 CSS
使其看起来像您的示例。
我还使用了@Ori Drori 的回答并设法让它发挥作用。随着 React 代码的增长,客户端浏览器必须在首次访问时下载的编译包也会增长。如果你处理不好,这会带来用户体验问题。
我在@Ori answer中添加的是在body标签的index.html onload属性中添加并执行onload函数,这样在浏览器全部加载完成后loader就消失了,请参阅下面的代码段:
<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>
使用 Pace 怎么样
在此处使用此 link 地址。
https://github.hubspot.com/pace/docs/welcome/
1.On他们的网站select你想要的样式然后粘贴在index.css
2.go 到 cdnjs 复制 Pace Js 的 link 并添加到 public/index.html
中的脚本标签3.It 自动检测网络负载并在浏览器顶部显示速度。
您还可以在css中修改高度和动画。
这是我的实现,基于答案
./public/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 中使用延迟加载轻松地做到这一点。 为此你必须使用懒惰和悬念来做出这样的反应。
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 呈现时开始了 您的加载组件将按照我们在 fallback 属性 中指定的方式显示多于。只需确保您仅在 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,你可以把你自己的 API 放在 fetch 中。
这个问题可以通过React的惰性特性轻松解决
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 库轻松添加加载屏幕。
也会影响页面切换,不过我觉得如果你想预加载第一页,自然也预加载其他页面。
此方法与 Suspense
的区别在于,使用此库,您可以在获取数据等的同时继续加载。
基本上,此方法与在组件内使用 isLoading
状态非常相似,但如果您有很多不同的页面,则更容易实现。
用法
在您的路由器部分从 react-router-loading
导入 Switch
和 Route
而不是 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
prop 的路由中提到的组件中的初始加载方法末尾添加 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 文档,source。
React.lazy function lets you render a dynamic import as a regular component.
This will automatically load the bundle containing the OtherComponent when this component is first rendered.
React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.
The lazy component should then be rendered inside a Suspense component, which allows us to show some fallback content (such as a loading indicator) while we’re waiting for the lazy component to load.
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
The fallback prop accepts any React elements that you want to render while waiting for the component to load. You can place the Suspense component anywhere above the lazy component. You can even wrap multiple lazy components with a single Suspense component.