mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-07 13:22:26 +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 React, {useState} from 'react';
|
||||||
import {renderRoutes} from 'react-router-config';
|
import {renderRoutes} from 'react-router-config';
|
||||||
import ReactListenerProvider from 'react-listener-provider';
|
|
||||||
|
|
||||||
import Head from '@docusaurus/Head'; // eslint-disable-line
|
import Head from '@docusaurus/Head'; // eslint-disable-line
|
||||||
import routes from '@generated/routes'; // eslint-disable-line
|
import routes from '@generated/routes'; // eslint-disable-line
|
||||||
import metadata from '@generated/metadata'; // eslint-disable-line
|
import metadata from '@generated/metadata'; // eslint-disable-line
|
||||||
import siteConfig from '@generated/docusaurus.config'; //eslint-disable-line
|
import siteConfig from '@generated/docusaurus.config'; //eslint-disable-line
|
||||||
import DocusaurusContext from '@docusaurus/context'; // eslint-disable-line
|
import DocusaurusContext from '@docusaurus/context'; // eslint-disable-line
|
||||||
|
import PendingNavigation from './PendingNavigation';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [context, setContext] = useState({});
|
const [context, setContext] = useState({});
|
||||||
|
@ -36,7 +36,9 @@ function App() {
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
<ReactListenerProvider>{renderRoutes(routes)}</ReactListenerProvider>
|
<PendingNavigation routes={routes}>
|
||||||
|
{renderRoutes(routes)}
|
||||||
|
</PendingNavigation>
|
||||||
</DocusaurusContext.Provider>
|
</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 React, {useEffect} from 'react';
|
||||||
import Perimeter from 'react-perimeter';
|
|
||||||
import {NavLink} from 'react-router-dom';
|
import {NavLink} from 'react-router-dom';
|
||||||
|
|
||||||
const internalRegex = /^\/(?!\/)/;
|
const internalRegex = /^\/(?!\/)/;
|
||||||
|
|
||||||
function Link(props) {
|
function Link(props) {
|
||||||
const {to, href, preloadProximity = 20} = props;
|
const {to, href} = props;
|
||||||
const targetLink = to || href;
|
const targetLink = to || href;
|
||||||
const isInternal = internalRegex.test(targetLink);
|
const isInternal = internalRegex.test(targetLink);
|
||||||
|
let preloaded = false;
|
||||||
|
|
||||||
const IOSupported =
|
const IOSupported =
|
||||||
typeof window !== 'undefined' && 'IntersectionObserver' in window;
|
typeof window !== 'undefined' && 'IntersectionObserver' in window;
|
||||||
|
@ -47,6 +47,13 @@ function Link(props) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onMouseEnter = () => {
|
||||||
|
if (!preloaded) {
|
||||||
|
window.docusaurus.preload(targetLink);
|
||||||
|
preloaded = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If IO is not supported. We prefetch by default (only once)
|
// If IO is not supported. We prefetch by default (only once)
|
||||||
if (!IOSupported && isInternal) {
|
if (!IOSupported && isInternal) {
|
||||||
|
@ -64,12 +71,12 @@ function Link(props) {
|
||||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||||
<a {...props} href={targetLink} />
|
<a {...props} href={targetLink} />
|
||||||
) : (
|
) : (
|
||||||
<Perimeter
|
<NavLink
|
||||||
padding={preloadProximity}
|
{...props}
|
||||||
onBreach={() => window.docusaurus.preload(targetLink)}
|
onMouseEnter={onMouseEnter}
|
||||||
once>
|
innerRef={handleRef}
|
||||||
<NavLink {...props} innerRef={handleRef} to={targetLink} />
|
to={targetLink}
|
||||||
</Perimeter>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,13 +55,12 @@
|
||||||
"is-wsl": "^1.1.0",
|
"is-wsl": "^1.1.0",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"mini-css-extract-plugin": "^0.4.1",
|
"mini-css-extract-plugin": "^0.4.1",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
"portfinder": "^1.0.13",
|
"portfinder": "^1.0.13",
|
||||||
"react-dev-utils": "^8.0.0",
|
"react-dev-utils": "^8.0.0",
|
||||||
"react-helmet": "^6.0.0-beta",
|
"react-helmet": "^6.0.0-beta",
|
||||||
"react-listener-provider": "^0.2.0",
|
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"react-loadable-ssr-addon": "^0.1.6",
|
"react-loadable-ssr-addon": "^0.1.6",
|
||||||
"react-perimeter": "^0.4.0",
|
|
||||||
"react-router-config": "^5.0.0",
|
"react-router-config": "^5.0.0",
|
||||||
"react-router-dom": "^5.0.0",
|
"react-router-dom": "^5.0.0",
|
||||||
"semver": "^6.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"
|
gauge "~2.7.3"
|
||||||
set-blocking "~2.0.0"
|
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:
|
nth-check@^1.0.2, nth-check@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.5.tgz#c54ac229dd66b5afe0de5acbe47647c3da692ff8"
|
||||||
integrity sha512-sudt2uq5P/2TznPV4Wtdi+Lnq3yaYW8LfvPKLM9BKD8jJNBkxMVyB0C9/GmVhLw7Jbdmndk/73n7XQGeN9A3QQ==
|
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:
|
react-loadable-ssr-addon@^0.1.6:
|
||||||
version "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"
|
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:
|
dependencies:
|
||||||
prop-types "^15.5.0"
|
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:
|
react-router-config@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.0.0.tgz#3d7e298dc64479bf9e1cc77080b8778e9a8d966c"
|
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