Open Riunshow opened 3 years ago
/** * hack 锚点 // usage const anchorProps = { type: 'scrollTop', container: '#container', interval: 0 } or const anchorProps = { type: 'scrollIntoView' } <Anchor name="xxx" {...anchorProps}>xxx</Anchor> */ import React from 'react' import PropTypes from 'prop-types' export const SCROLL_INTO_VIEW = 'scrollIntoView' export const SCROLL_TOP = 'scrollTop' const getSearch = () => { const { location } = window const result = location.href.split('?')[1] if (result) { return `?${result}` } } const getSearchParams = (key) => { const params = new URLSearchParams(getSearch()) return params.get(key) } const isNumber = (val) => { // IE9 toString.call() 报错:调用的对象无效 // 应为 window.toString !== Object.prototype.toString if (Object.prototype.toString.call(val) === '[object Number]') { if (isNaN(val)) { return false } return true } return false } // 为了兼容 IE Edge Chrome const setScrollTop = (val) => { console.log(val) document.documentElement.scrollTop = val window.pageYOffset = val document.body.scrollTop = val } class Anchor extends React.Component { constructor(props) { super(props) this.anchorRef = React.createRef() this.handleHashChange = this.handleHashChange.bind(this) this.scroll = this.scroll.bind(this) this.scrollIntoView = this.scrollIntoView.bind(this) this.scrollTop = this.scrollTop.bind(this) } componentDidMount() { const { anchorKey } = this.props if (getSearchParams(anchorKey)) { this.scroll() } // Chrome keeps track of where you've been // https://developer.mozilla.org/en-US/docs/Web/API/History if ('scrollRestoration' in history) { history.scrollRestoration = 'manual' } window.addEventListener('hashchange', this.handleHashChange) this.props.onGetBoundingClientRect && document.querySelector('#container').addEventListener('scroll', this.handleDOMMouseScroll, false) } componentWillUnmount() { if ('scrollRestoration' in history) { history.scrollRestoration = 'auto' } window.removeEventListener('hashchange', this.handleHashChange) this.props.onGetBoundingClientRect && document.querySelector('#container').removeEventListener('scroll', this.handleDOMMouseScroll) } handleHashChange() { this.scroll() } // 获取当前可视区域的 name handleDOMMouseScroll = () => { const dom = this.anchorRef.current const { name } = this.props if (dom && dom.getBoundingClientRect().top < 200 && dom.getBoundingClientRect().top > 50) { if (this.props.onGetBoundingClientRect) { this.props.onGetBoundingClientRect(name) } } } scroll() { const { type } = this.props if (type === SCROLL_INTO_VIEW) { this.scrollIntoView() } if (type === SCROLL_TOP) { this.scrollTop() } } scrollIntoView() { const { name, anchorKey, scrollIntoViewOption } = this.props const anchor = getSearchParams(anchorKey) if (name === anchor) { const dom = this.anchorRef.current if (dom.scrollIntoView) { setTimeout(() => { dom.scrollIntoView(scrollIntoViewOption) }, 0) } } } scrollTop() { const { name, anchorKey, container, interval } = this.props const anchor = getSearchParams(anchorKey) if (name === anchor) { if (!isNumber(interval)) { throw new Error('interval must be a number') } const dom = this.anchorRef.current const scrollTop = dom.offsetTop + Number(interval) if (container) { const cont = document.querySelector(container) if (!cont) { throw new Error('container can\'t match any element') } setTimeout(() => { cont.scrollTop = scrollTop }, 0) } else { setTimeout(() => { setScrollTop(scrollTop) }, 0) } } } render() { const { children } = this.props return ( <div ref={this.anchorRef}> {children} </div> ) } } Anchor.defaultProps = { anchorKey: '_to', type: SCROLL_INTO_VIEW, scrollIntoViewOption: true, interval: 0 } Anchor.protoTypes = { anchorKey: PropTypes.string, type: PropTypes.oneOf([SCROLL_INTO_VIEW, SCROLL_TOP]), scrollIntoViewOption: PropTypes.oneOf([ PropTypes.bool, PropTypes.object ]), container: PropTypes.string, interval: PropTypes.number, onGetBoundingClientRect: PropTypes.func } export default Anchor