From 94e476cee954fe4cbf29618d18b80f2c451f52b7 Mon Sep 17 00:00:00 2001 From: Troy Li Date: Fri, 13 Dec 2019 18:21:02 +0800 Subject: [PATCH] perf(umi-plugin): scroll bug & active for slugs (#39) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 修复 hash 路由锚点跳转,优化 affix active 跟随 * fix: affix 切换页面后无法动态高亮 --- package.json | 1 + packages/umi-plugin-father-doc/package.json | 2 + packages/umi-plugin-father-doc/src/index.ts | 1 + .../src/themes/default/layout.tsx | 95 +++++++++++++++++-- 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 2bbb31e666..fa4426b599 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "private": true, "scripts": { "dev": "cross-env BROWSER=none node ./packages/father-doc/bin/father-doc.js dev", + "watch": "father-build build --watch", "doc:build": "cross-env BROWSER=none node ./packages/father-doc/bin/father-doc.js build", "doc:deploy": "bash ./scripts/deploy_doc.sh", "bootstrap": "lerna bootstrap", diff --git a/packages/umi-plugin-father-doc/package.json b/packages/umi-plugin-father-doc/package.json index a09be65990..8ca3ac87bd 100644 --- a/packages/umi-plugin-father-doc/package.json +++ b/packages/umi-plugin-father-doc/package.json @@ -33,6 +33,7 @@ "deepmerge": "^4.2.2", "glob": "^7.1.6", "hast-util-to-html": "^6.0.2", + "intersection-observer": "^0.7.0", "js-yaml": "^3.13.1", "lodash": "^4.17.15", "lz-string": "^1.4.4", @@ -45,6 +46,7 @@ "remark-frontmatter": "^1.3.2", "remark-parse": "^7.0.1", "remark-rehype": "^5.0.0", + "scrollama": "^2.1.3", "sylvanas": "^0.4.2", "umi": "^2.12.3", "umi-build-dev": "^1.16.5", diff --git a/packages/umi-plugin-father-doc/src/index.ts b/packages/umi-plugin-father-doc/src/index.ts index 1877315b5b..7572ffac10 100644 --- a/packages/umi-plugin-father-doc/src/index.ts +++ b/packages/umi-plugin-father-doc/src/index.ts @@ -7,6 +7,7 @@ import getRouteConfig from './routes/getRouteConfig'; import getMenuFromRoutes from './routes/getMenuFromRoutes'; import getHostPkgAlias from './utils/getHostPkgAlias'; import { setUserExtraBabelPlugin } from './transformer/demo'; +import 'intersection-observer'; export interface IFatherDocOpts { title?: string; diff --git a/packages/umi-plugin-father-doc/src/themes/default/layout.tsx b/packages/umi-plugin-father-doc/src/themes/default/layout.tsx index 407c637274..3851f6c809 100644 --- a/packages/umi-plugin-father-doc/src/themes/default/layout.tsx +++ b/packages/umi-plugin-father-doc/src/themes/default/layout.tsx @@ -2,8 +2,10 @@ import React, { Component } from 'react'; import { RouterTypes } from 'umi'; import Link from 'umi/link'; import NavLink from 'umi/navlink'; +import scrollama from 'scrollama'; import 'prismjs/themes/prism.css'; import { IMenuItem } from '../../routes/getMenuFromRoutes'; +import { parse } from 'querystring'; import './layout.less'; export interface ILayoutProps { @@ -15,11 +17,73 @@ export interface ILayoutProps { }; } -export default class Layout extends Component { +export interface ILayoutState { + localActive : string; +} + +export default class Layout extends Component { + + private scrollama; + + constructor(props: ILayoutProps & RouterTypes) { + super(props); + this.state = { + localActive: '' + }; + }; + + scrollIntoAnchor() { + // 如果存在 anchor 滚动过去 + const anchor = parse(this.props.location.search.slice(1)).section as string; + if(anchor){ + window.setTimeout(()=>{ + const dom = document.getElementById(anchor); + if(dom){ + dom.scrollIntoView({ behavior: 'smooth' }); + } + }, 20) + } + } + + initializeScrollma() { + // instantiate the scrollama + this.scrollama = scrollama(); + const { slugs } = this.getMetaForCurrentPath(); + + // setup the instance, pass callback functions + this.scrollama + .setup({ + step: '[id]', + offset: 0.01, + }) + .onStepEnter(response => { + const { element } = response; + if(slugs.map(ele => ele.heading).includes(element.id)){ + this.setState({localActive: element.id }) + } + }); + + // setup resize event + window.addEventListener('resize', this.scrollama.resize); + } + componentDidMount() { - window.g_history.listen(() => { - window.scrollTo(0, 0); - }); + window.scrollTo(0, 0); + this.initializeScrollma(); + this.scrollIntoAnchor(); + } + + componentDidUpdate(prevProps: ILayoutProps & RouterTypes, prevState: ILayoutState) { + if(prevProps.location.search !== this.props.location.search) { + this.scrollIntoAnchor(); + } + if(prevProps.location.hash !== this.props.location.hash || + prevProps.location.pathname !== this.props.location.pathname ){ + window.scrollTo(0, 0); + this.scrollama.destroy(); + this.initializeScrollma(); + this.setState({localActive: ''}); + } } getMetaForCurrentPath = (routes = (this.props.route as any).routes) => { @@ -87,8 +151,19 @@ export default class Layout extends Component { ); } - renderAffix(meta, hash) { + renderAffix(meta, search) { const { slugs = [] } = meta; + const { localActive = '' } = this.state; + const section = parse(search).section; + + let realActive = ''; + if(localActive) { + realActive = localActive; + } else if (section) { + realActive = section as string; + } else { + realActive = slugs[0].heading; + } const jumper = slugs.map(item => { return ( @@ -96,11 +171,11 @@ export default class Layout extends Component { key={item.heading} title={item.value} data-depth={item.depth} - className={`#${encodeURI(item.heading)}` === hash ? 'active' : ''} + className={realActive === item.heading ? 'active' : ''} > - + {item.value} - + ); }); @@ -112,7 +187,7 @@ export default class Layout extends Component { render() { const { children, - location: { hash }, + location: { search }, } = this.props; const meta = this.getMetaForCurrentPath(); const showSidebar = meta.sidebar !== false; @@ -125,7 +200,7 @@ export default class Layout extends Component { data-show-slugs={showSlugs} > {showSidebar && this.renderSideMenu()} - {showSlugs && this.renderAffix(meta, hash)} + {showSlugs && this.renderAffix(meta, search.slice(1))} {children} );