/**
* 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