mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-31 09:57:03 +02:00
feat(v2): 🔥 🔥 better loading UX ⚡️ (#1383)
* feat(v2): render old screen while loading * replace and remove stuff * spacing * add spinner back, just as a fallback * nits * Turn off loading spinner on top right * address review
This commit is contained in:
parent
23e56f61f5
commit
10908009e3
5 changed files with 109 additions and 24 deletions
|
@ -7,13 +7,13 @@
|
|||
|
||||
import React, {useState} from 'react';
|
||||
import {renderRoutes} from 'react-router-config';
|
||||
import ReactListenerProvider from 'react-listener-provider';
|
||||
|
||||
import Head from '@docusaurus/Head'; // eslint-disable-line
|
||||
import routes from '@generated/routes'; // eslint-disable-line
|
||||
import metadata from '@generated/metadata'; // eslint-disable-line
|
||||
import siteConfig from '@generated/docusaurus.config'; //eslint-disable-line
|
||||
import DocusaurusContext from '@docusaurus/context'; // eslint-disable-line
|
||||
import PendingNavigation from './PendingNavigation';
|
||||
|
||||
function App() {
|
||||
const [context, setContext] = useState({});
|
||||
|
@ -36,7 +36,9 @@ function App() {
|
|||
type="text/css"
|
||||
/>
|
||||
</Head>
|
||||
<ReactListenerProvider>{renderRoutes(routes)}</ReactListenerProvider>
|
||||
<PendingNavigation routes={routes}>
|
||||
{renderRoutes(routes)}
|
||||
</PendingNavigation>
|
||||
</DocusaurusContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
84
packages/docusaurus/lib/client/PendingNavigation.js
Normal file
84
packages/docusaurus/lib/client/PendingNavigation.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {Route, withRouter} from 'react-router-dom';
|
||||
import nprogress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import preload from './preload';
|
||||
|
||||
nprogress.configure({showSpinner: false});
|
||||
|
||||
class PendingNavigation extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.progressBarTimeout = null;
|
||||
this.state = {
|
||||
previousLocation: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const navigated = nextProps.location !== this.props.location;
|
||||
const {routes, delay = 1000} = this.props;
|
||||
|
||||
if (navigated) {
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
this.startProgressBar(delay);
|
||||
// save the location so we can render the old screen
|
||||
this.setState({
|
||||
previousLocation: this.props.location,
|
||||
});
|
||||
|
||||
// load data while the old screen remains
|
||||
preload(routes, nextProps.location.pathname)
|
||||
.then(() => {
|
||||
this.setState(
|
||||
{
|
||||
previousLocation: null,
|
||||
},
|
||||
this.stopProgressBar,
|
||||
);
|
||||
})
|
||||
.catch(e => console.warn(e));
|
||||
}
|
||||
}
|
||||
|
||||
clearProgressBarTimeout() {
|
||||
if (this.progressBarTimeout) {
|
||||
clearTimeout(this.progressBarTimeout);
|
||||
this.progressBarTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
startProgressBar(delay) {
|
||||
this.clearProgressBarTimeout();
|
||||
this.progressBarTimeout = setTimeout(() => {
|
||||
nprogress.start();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
stopProgressBar() {
|
||||
this.clearProgressBarTimeout();
|
||||
nprogress.done();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children, location} = this.props;
|
||||
const {previousLocation} = this.state;
|
||||
|
||||
// use a controlled <Route> to trick all descendants into
|
||||
// rendering the old location
|
||||
return (
|
||||
<Route location={previousLocation || location} render={() => children} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(PendingNavigation);
|
|
@ -6,15 +6,15 @@
|
|||
*/
|
||||
|
||||
import React, {useEffect} from 'react';
|
||||
import Perimeter from 'react-perimeter';
|
||||
import {NavLink} from 'react-router-dom';
|
||||
|
||||
const internalRegex = /^\/(?!\/)/;
|
||||
|
||||
function Link(props) {
|
||||
const {to, href, preloadProximity = 20} = props;
|
||||
const {to, href} = props;
|
||||
const targetLink = to || href;
|
||||
const isInternal = internalRegex.test(targetLink);
|
||||
let preloaded = false;
|
||||
|
||||
const IOSupported =
|
||||
typeof window !== 'undefined' && 'IntersectionObserver' in window;
|
||||
|
@ -47,6 +47,13 @@ function Link(props) {
|
|||
}
|
||||
};
|
||||
|
||||
const onMouseEnter = () => {
|
||||
if (!preloaded) {
|
||||
window.docusaurus.preload(targetLink);
|
||||
preloaded = true;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// If IO is not supported. We prefetch by default (only once)
|
||||
if (!IOSupported && isInternal) {
|
||||
|
@ -64,12 +71,12 @@ function Link(props) {
|
|||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
<a {...props} href={targetLink} />
|
||||
) : (
|
||||
<Perimeter
|
||||
padding={preloadProximity}
|
||||
onBreach={() => window.docusaurus.preload(targetLink)}
|
||||
once>
|
||||
<NavLink {...props} innerRef={handleRef} to={targetLink} />
|
||||
</Perimeter>
|
||||
<NavLink
|
||||
{...props}
|
||||
onMouseEnter={onMouseEnter}
|
||||
innerRef={handleRef}
|
||||
to={targetLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,13 +55,12 @@
|
|||
"is-wsl": "^1.1.0",
|
||||
"lodash": "^4.17.11",
|
||||
"mini-css-extract-plugin": "^0.4.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"portfinder": "^1.0.13",
|
||||
"react-dev-utils": "^8.0.0",
|
||||
"react-helmet": "^6.0.0-beta",
|
||||
"react-listener-provider": "^0.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"react-loadable-ssr-addon": "^0.1.6",
|
||||
"react-perimeter": "^0.4.0",
|
||||
"react-router-config": "^5.0.0",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"semver": "^6.0.0",
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -9509,6 +9509,11 @@ npm-which@^3.0.1:
|
|||
gauge "~2.7.3"
|
||||
set-blocking "~2.0.0"
|
||||
|
||||
nprogress@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1"
|
||||
integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E=
|
||||
|
||||
nth-check@^1.0.2, nth-check@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
|
||||
|
@ -10986,11 +10991,6 @@ react-is@^16.8.1, react-is@^16.8.4:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.5.tgz#c54ac229dd66b5afe0de5acbe47647c3da692ff8"
|
||||
integrity sha512-sudt2uq5P/2TznPV4Wtdi+Lnq3yaYW8LfvPKLM9BKD8jJNBkxMVyB0C9/GmVhLw7Jbdmndk/73n7XQGeN9A3QQ==
|
||||
|
||||
react-listener-provider@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-listener-provider/-/react-listener-provider-0.2.0.tgz#fb6ce123f9e20ad948f75906ddf647db1df31f18"
|
||||
integrity sha1-+2zhI/niCtlI91kG3fZH2x3zHxg=
|
||||
|
||||
react-loadable-ssr-addon@^0.1.6:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon/-/react-loadable-ssr-addon-0.1.6.tgz#de3e45f2b3876dc7b9afae19138370f0588b7300"
|
||||
|
@ -11003,13 +11003,6 @@ react-loadable@^5.5.0:
|
|||
dependencies:
|
||||
prop-types "^15.5.0"
|
||||
|
||||
react-perimeter@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-perimeter/-/react-perimeter-0.4.0.tgz#be86b5560bb96650272aaf7bd37639460b82645b"
|
||||
integrity sha512-hSjth5oh4L/ZVR6PfR9eu7B1lil0AteGXLn7ki7YNJI7bkKlZf28pwFyFKNIXkoRJT3FiD01kGYkAmIz1k8b9A==
|
||||
optionalDependencies:
|
||||
react-listener-provider "^0.2.0"
|
||||
|
||||
react-router-config@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.0.0.tgz#3d7e298dc64479bf9e1cc77080b8778e9a8d966c"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue