React - Display loading screen while DOM is rendering?

前端 未结 19 1679
我在风中等你
我在风中等你 2020-11-28 00:29

This is an example from Google Adsense application page. The loading screen displayed before the main page showed after.

I don\'t know how to do the same th

19条回答
  •  一生所求
    2020-11-28 01:00

    The goal

    When the html page is rendered, display a spinner immediately (while React loads), and hide it after React is ready.

    Since the spinner is rendered in pure HTML/CSS (outside of the React domain), React shouldn't control the showing/hiding process directly, and the implementation should be transparent to React.

    Solution 1 - the :empty pseudo-class

    Since you render react into a DOM container -

    , you can add a spinner to that container, and when react will load and render, the spinner will disappear.

    You can't add a DOM element (a div for example) inside the react root, since React will replace the contents of the container as soon as ReactDOM.render() is called. Even if you render null, the content would still be replaced by a comment - . This means that if you want to display the loader while the main component mounts, data is loading, but nothing is actually rendered, a loader markup placed inside the container (

    for example) would not work.

    A workaround is to add the spinner class to the react container, and use the :empty pseudo class. The spinner will be visible, as long as nothing is rendered into the container (comments don't count). As soon as react renders something other than comment, the loader will disappear.

    Example 1

    In the example you can see a component that renders null until it's ready. The container is the loader as well -

    , and the loader's class will only work if it's :empty (see comments in code):

    class App extends React.Component {
      state = {
        loading: true
      };
    
      componentDidMount() {
        // this simulates an async action, after which the component will render the content
        demoAsyncCall().then(() => this.setState({ loading: false }));
      }
      
      render() {
        const { loading } = this.state;
        
        if(loading) { // if your component doesn't have to wait for an async action, remove this block 
          return null; // render null when app is not ready
        }
        
        return (
          
    I'm the app
    ); } } function demoAsyncCall() { return new Promise((resolve) => setTimeout(() => resolve(), 2500)); } ReactDOM.render( , 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);
      }
    }
    
    
    
    

    Example 2

    A variation on using the :empty pseudo class to show/hide a selector, is setting the spinner as a sibling element to the app container, and showing it as long as the container is empty using the 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 (
          
    I'm the app
    ); } } function demoAsyncCall() { return new Promise((resolve) => setTimeout(() => resolve(), 2500)); } ReactDOM.render( , 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);
      }
    }
    
    
    
    


    Solution 2 - Pass spinner "handlers" as props

    To have a more fine grained control over the spinners display state, create two functions showSpinner and hideSpinner, and pass them to the root container via props. The functions can manipulate the DOM, or do whatever needed to control the spinner. In this way, React is not aware of the "outside world", nor needs to control the DOM directly. You can easily replace the functions for testing, or if you need to change the logic, and you can pass them to other components in the React tree.

    Example 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 (
          
    I'm the app
    ); } } // 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( , 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);
      }
    }
    
    
    
    

    Example 2 - hooks

    This example uses the useEffect hook to hide the spinner after the component mounts.

    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 (
        
    I'm the app
    ); } // 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( , 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);
      }
    }
    
    
    
    

提交回复
热议问题