反应-在渲染DOM时显示加载屏幕吗?


149

这是来自Google Adsense应用程序页面的示例。在主页之前显示的加载屏幕在之后显示。

在此处输入图片说明

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

更新时间

我通过将屏幕加载程序放入index.html并在React componentDidMount()生命周期方法中将其删除来举例说明了我的方法。

示例react-loading-screen


显示您想要在普通js中显示的内容,然后将其隐藏或在安装react时从DOM中删除。您需要做的就是将其隐藏在React代码中。
FurkanO '16

这简直太棒了!谢谢。
Arman Karimi

Answers:


100

可以通过将加载图标放置在html文件中(例如index.html)来完成操作,以便用户在html文件加载后立即看到该图标。

当您的应用程序完成加载后,您只需在生命周期挂钩中删除该加载图标即可,我通常在中执行此操作componentDidMount


11
如果将根组件安装到该图标的父节点,甚至无需手动将其删除。React将清除mount节点的子节点,并将其自己的新呈现的DOM放在那里。
rishat

6
我没有将图标放在React应用的根节点内,只是感觉不对
kkkkkkk 2016年

170

目标

呈现html页面时,立即显示微调器(在React加载时),并在React准备好后将其隐藏。

由于微调器是用纯HTML / CSS呈现的(在React域之外),因此React不应直接控制显示/隐藏过程,并且实现应对React透明。

解决方案1-:empty伪类

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

您不能在react根目录内添加DOM元素(例如div),因为React会在ReactDOM.render()调用后立即替换容器的内容。即使您渲染null,内容仍将被注释-代替 <!-- react-empty: 1 -->。这意味着,如果要在安装主要组件时显示加载程序,则正在加载数据,但实际上未呈现任何内容,因此放置在容器内的加载程序标记(<div id="app"><div class="loader"></div></div>例如)将不起作用。

一种解决方法是将Spinner类添加到react容器,并使用:empty伪类。只要没有任何内容呈现到容器中,微调器就会可见(注释不计算在内)。一旦react提供注释以外的内容,加载程序就会消失。

例子1

在示例中,您可以看到一个null在准备就绪之前将进行渲染的组件。容器也是加载器- <div id="app" class="app"></div>,并且加载器的类只有在容器加载器的类才起作用:empty(请参见代码中的注释):

例子2

使用:empty伪类显示/隐藏选择器的一种变体是将微调器设置为应用容器的同级元素,并使用相邻的同级组合器+)在容器为空的情况下显示它:


解决方案2-通过微调器“处理程序”作为道具

要对微调器的显示状态进行更细粒度的控制,请创建两个函数showSpinnerhideSpinner,然后将它们通过props传递到根容器。这些函数可以操纵DOM,或执行控制微调器所需的任何操作。这样,React不会意识到“外部世界”,也不需要直接控制DOM。您可以轻松地替换用于测试的功能,或者如果您需要更改逻辑,则可以将其传递给React树中的其他组件。

例子1

示例2-钩子

此示例使用useEffect挂钩在组件安装后隐藏微调器。


您能说明最后两个代码段应该在哪里吗?第一个显然是在react组件的javascript src文件中,我猜第三个在所说的js文件要呈现的html模板中,但是第二个在哪里呢?
levraininjaneer

1
第二个是CSS。我使用了全局CSS,但是可以在CSS中使用CSS模块或CSS。第三个是HTML文件,如果需要,它可能包含微调框标记(第二个示例)。
Ori Drori '18

如果考虑到性能,则超时效果不佳。
枯叶

4
@dryleaf-setTimeout不是解决方案的一部分。它模拟呈现内容之前等待异步动作。
Ori Drori '18

我使用类似的方法。我在webpack中找不到任何可以帮助我关闭加载程序所需的CSS文件的缓存的内容。你能帮我吗?
hamza-jutt

40

解决方法是:

在渲染函数中执行以下操作:

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

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

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

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


当我们已经调用ajax方法将数据加载到子组件时。在子组件数据填充之前调用componentDidMount。我们如何克服这类问题?
dush88c

2
对于Mounting Life cyle很好,您是否想添加一些更新生命周期的内容?
zakir

我是否必须在所有页面中或仅在应用程序条目中执行此操作
Pedro JR

16

如果有人在寻找上述用例的嵌入式,零配置和零依赖库,请尝试一下speed.js(http://github.hubspot.com/pace/docs/welcome/)。

它会自动挂接到事件(ajax,readyState,历史记录pushstate,js事件循环等)并显示可自定义的加载器。

与我们的React / Relay项目配合良好(使用react-router处理中继更改,中继请求) (不隶属;为我们的项目使用了speed.js,效果很好)


嘿! 你能告诉我如何与react一起使用吗?
uneet7 '19

只需将脚本附加到public/index.html并选择样式即可。这简直是​​简单,令人惊叹的插件。谢谢。
PJ3'4

没有这个答案,我将找不到步伐。包含它是如此容易,并且有了一点CSS魔术和一些事件附件,我能够在过渡期间阻止/禁用该应用程序并自定义微调器。

12

当您的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,事情就不会那么复杂了,因为用户在页面加载后便会真正看到真实的应用程序。


这也可行,我有两个地方发生加载。一种是庞大的应用程序。接下来是准备工作(安装各种组件。)所以我要执行一个闪烁的步骤,因为app.render接管了该动画,并且动画被重置了(真的被替换了。)是否有办法避免该闪烁?React会一一比较DOM吗?但是据我了解,React在标签中添加了各种私有数据……
Alexis Wilke

12

这将在ReactDOM.render()控制 之前发生<div>。也就是说,您的应用尚未安装完成。

因此,您可以将加载程序添加index.html到根目录下的文件中<div>。这将在屏幕上显示,直到React接管为止。

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

您无需在任何生命周期方法中将其删除。正如我们在下面的GIF中看到的那样,React将用您的render 替换其根目录的 所有子级。<div><App/>

CodeSandbox上的示例

在此处输入图片说明

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

使用debugger检查之前的页面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);

一个美丽而优雅的解决方案
Gal Margalit,

1
很高兴能帮到您!
cbdeveloper

9

如今,我们还可以在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;

5

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

4

我最近不得不处理该问题,并想出了一个解决方案,对我来说效果很好。但是,我在上面尝试了@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文件内部(在render函数中):

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

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

如何运作?

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

更为重要的是添加- !spinner.hasAttribute('hidden')条件阻止hidden每次安装组件时都向微调器添加属性,因此,当整个应用加载时,实际上只会添加一次。


4

我正在使用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()


4

我也在我的应用程序中使用React。对于请求,我正在使用axios拦截器,因此使加载程序屏幕(显示示例为全页)的绝佳方法是将类或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添加到其他元素,而不是您喜欢的主体)-您可以将背景颜色设置为不透明或透明,等等。例如:

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

custom-loader:after {
    background: #000000;
    content: "Loading content...";
    position: fixed;
    color: white;
    ...
}

3

请编辑您的index.html文件位置的公共文件夹。将图像复制到公用文件夹中与index.html相同的位置。然后将包含<div id="root"> </div>标签的index.html内容的一部分替换为 以下给定的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替换。


2

您不需要那么多努力,这是一个基本示例。

<!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使它看起来像你的榜样。


1

最重要的问题是:“加载”是什么意思?如果您正在谈论要安装的物理元素,那么这里的第一个答案很好。但是,如果您的应用程序要做的第一件事是检查身份验证,那么您真正加载的是来自后端的数据,无论用户是否通过了将其标记为授权或未授权用户的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();
 }

}

如果state.loading是正确的,我们将始终看到加载屏幕。在componentDidMount上,我们调用getTodos函数,该函数是一个动作创建器,当我们获得响应时(可能是错误),它会转变为state.loading falsy。我们的组件将更新,再次调用render,由于if语句,这一次没有加载屏幕。


1

react app的启动基于主捆绑包下载。只有在将主捆绑包下载到浏览器中之后,React应用才会启动。在延迟加载体系结构的情况下甚至如此。但是事实是我们无法确切说明任何捆绑包的名称。因为当您运行“ npm run build”命令时,webpack会在每个捆绑软件的末尾添加一个哈希值。当然,可以通过更改哈希设置来避免这种情况,但是它将严重影响浏览器中的缓存数据问题。由于相同的捆绑软件名称,浏览器可能不采用新版本。。我们需要一个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,
...

您可能需要使用“弹出”命令来获取配置文件。最新的webpack可以选择配置HtmlWebpackPlugin而无需弹出项目。 在此处输入图片说明


1

我还使用了@Ori Drori的答案,并设法使其起作用。随着您的React代码的增长,客户端浏览器在首次访问时必须下载的已编译捆绑包也将随之增加。如果处理不当,则会带来用户体验问题。

我在@Ori答案中添加的内容是在body标签的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>

1

怎样使用Pace

在此使用此链接地址。

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

1.在他们的网站上选择所需的样式并粘贴到index.css中

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

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

您还可以在CSS中修改高度和动画。


太棒了,可以立即集成。
UzumakiL
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.