Last active
May 9, 2021 14:17
-
-
Save megamaddu/d1ca3d78b9448004455562af1f04f81e to your computer and use it in GitHub Desktop.
Scroll Manager for React Router v4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { func, node, number, object, shape, string } from 'prop-types' | |
import { withRouter } from 'react-router' | |
import debounceFn from 'lodash/debounce' | |
class ScrollManager extends React.Component { | |
static propTypes = { | |
children: node.isRequired, | |
history: shape({ | |
action: string.isRequired, | |
push: func.isRequired, | |
replace: func.isRequired | |
}).isRequired, | |
location: object, | |
onLocationChange: func, | |
scrollCaptureDebounce: number, | |
scrollSyncDebounce: number, | |
scrollSyncAttemptLimit: number | |
} | |
static defaultProps = { | |
scrollCaptureDebounce: 50, | |
scrollSyncDebounce: 100, | |
scrollSyncAttemptLimit: 5 | |
} | |
constructor (props) { | |
super(props) | |
this.scrollSyncData = { | |
x: 0, | |
y: 0, | |
attemptsRemaining: props.scrollSyncAttemptLimit | |
} | |
const scrollCapture = () => { | |
requestAnimationFrame(() => { | |
const { pageXOffset: x, pageYOffset: y } = window | |
const { pathname, search } = this.props.location | |
// use browser history instead of router history | |
// to avoid infinite history.replace loop | |
const historyState = window.history.state || {} | |
const { state = {} } = historyState | |
if (!state.scroll || state.scroll.x !== pageXOffset || state.scroll.y !== pageYOffset) { | |
window.history.replaceState( | |
{ | |
...historyState, | |
state: { ...state, scroll: { x, y } } | |
}, | |
null, | |
pathname + search | |
) | |
} | |
}) | |
} | |
const _scrollSync = () => { | |
requestAnimationFrame(() => { | |
const { x, y, attemptsRemaining } = this.scrollSyncData | |
if (attemptsRemaining < 1) { | |
return | |
} | |
const { pageXOffset, pageYOffset } = window | |
if (y < window.document.body.scrollHeight && (x !== pageXOffset || y !== pageYOffset)) { | |
window.scrollTo(x, y) | |
this.scrollSyncData.attemptsRemaining = attemptsRemaining - 1 | |
_scrollSync() | |
} | |
}) | |
} | |
const scrollSync = (x = 0, y = 0) => { | |
this.scrollSyncData = { x, y, attemptsRemaining: this.props.scrollSyncAttemptLimit } | |
_scrollSync() | |
} | |
this.debouncedScroll = debounceFn(scrollCapture, props.scrollCaptureDebounce) | |
this.debouncedScrollSync = debounceFn(scrollSync, props.scrollSyncDebounce) | |
} | |
componentWillMount () { | |
const { location, onLocationChange } = this.props | |
if (onLocationChange) { | |
onLocationChange(location) | |
} | |
} | |
componentDidMount () { | |
this.onPop(this.props) | |
window.addEventListener('scroll', this.debouncedScroll, { passive: true }) | |
} | |
componentWillUnmount () { | |
this.scrollSyncPending = false | |
window.removeEventListener('scroll', this.debouncedScroll, { passive: true }) | |
} | |
componentWillReceiveProps (nextProps) { | |
switch (nextProps.history.action) { | |
case 'PUSH': | |
case 'REPLACE': this.onPush(); break | |
case 'POP': this.onPop(nextProps); break | |
default: | |
console.warn(`Unrecognized location change action! "${nextProps.history.action}"`) | |
} | |
if (nextProps.onLocationChange) { | |
nextProps.onLocationChange(nextProps.location) | |
} | |
} | |
onPush () { | |
this.debouncedScrollSync(0, 0) | |
} | |
onPop ({ location: { state = {} } }) { | |
// attempt location restore | |
const { x = 0, y = 0 } = state.scroll || {} | |
this.debouncedScrollSync(x, y) | |
} | |
render () { | |
return this.props.children | |
} | |
} | |
export default withRouter(ScrollManager) |
Good point! (also this is more for reference than anything else, I recommend using a maintained library instead)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You should append
window.location.search
here: https://gist.github.com/spicydonuts/d1ca3d78b9448004455562af1f04f81e#file-scroll-manager-js-L52.Otherwise it'll remove query params when scrolling.