Created
June 15, 2016 23:16
-
-
Save ro-savage/2ee466eba354b114fb7f33ebddc2c8d1 to your computer and use it in GitHub Desktop.
Updated react-router-scroll
This file contains hidden or 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, { PropTypes } from 'react' | |
| import { Provider } from 'react-redux' | |
| import { Router, applyRouterMiddleware } from 'react-router' | |
| import useScroll from '../helpers/ScrollBehaviourContainer' | |
| import getRoutes from '../routes' | |
| const Root = (props) => { | |
| return ( | |
| <Provider store={props.store}> | |
| <Router | |
| history={props.history} | |
| routes={getRoutes()} | |
| render={applyRouterMiddleware(useScroll(undefined, 'mainContent'))} | |
| /> | |
| </Provider> | |
| ) | |
| } | |
| Root.propTypes = { | |
| history: PropTypes.object.isRequired, | |
| store: PropTypes.object.isRequired, | |
| } | |
| export default Root |
This file contains hidden or 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
| /* eslint-disable */ | |
| import off from 'dom-helpers/events/off'; | |
| import on from 'dom-helpers/events/on'; | |
| import scrollLeft from 'dom-helpers/query/scrollLeft'; | |
| import scrollTop from 'dom-helpers/query/scrollTop'; | |
| import requestAnimationFrame from 'dom-helpers/util/requestAnimationFrame'; | |
| import { PUSH } from 'history/lib/Actions'; | |
| import { readState, saveState } from 'history/lib/DOMStateStorage'; | |
| // FIXME: Stop using this gross hack. This won't collide with any actual | |
| // history location keys, but it's dirty to sneakily use the same storage here. | |
| const KEY_PREFIX = 's/'; | |
| // Try at most this many times to scroll, to avoid getting stuck. | |
| const MAX_SCROLL_ATTEMPTS = 2; | |
| export default class ScrollBehavior { | |
| constructor(history, getCurrentLocation, elTargetId) { | |
| this._history = history; | |
| this._getCurrentLocation = getCurrentLocation; | |
| this._elTargetId = elTargetId || false | |
| // This helps avoid some jankiness in fighting against the browser's | |
| // default scroll behavior on `POP` transitions. | |
| /* istanbul ignore if: not supported by any browsers on Travis */ | |
| if ('scrollRestoration' in window.history) { | |
| this._oldScrollRestoration = window.history.scrollRestoration; | |
| window.history.scrollRestoration = 'manual'; | |
| } else { | |
| this._oldScrollRestoration = null; | |
| } | |
| this._savePositionHandle = null; | |
| this._checkScrollHandle = null; | |
| this._scrollTarget = null; | |
| this._numScrollAttempts = 0; | |
| // We have to listen to each scroll update rather than to just location | |
| // updates, because some browsers will update scroll position before | |
| // emitting the location change. | |
| on(window, 'scroll', this._onScroll); | |
| this._unlistenBefore = history.listenBefore(() => { | |
| if (this._savePositionHandle !== null) { | |
| requestAnimationFrame.cancel(this._savePositionHandle); | |
| this._savePositionHandle = null; | |
| } | |
| }); | |
| } | |
| stop() { | |
| /* istanbul ignore if: not supported by any browsers on Travis */ | |
| if (this._oldScrollRestoration) { | |
| window.history.scrollRestoration = this._oldScrollRestoration; | |
| } | |
| off(window, 'scroll', this._onScroll); | |
| this._cancelCheckScroll(); | |
| this._unlistenBefore(); | |
| } | |
| updateScroll(scrollPosition) { | |
| // Whatever we were doing before isn't relevant any more. | |
| this._cancelCheckScroll(); | |
| if (scrollPosition && !Array.isArray(scrollPosition)) { | |
| this._scrollTarget = this._getDefaultScrollTarget(); | |
| } else { | |
| this._scrollTarget = scrollPosition; | |
| } | |
| // Check the scroll position to see if we even need to scroll. | |
| this._onScroll(); | |
| if (!this._scrollTarget) { | |
| return; | |
| } | |
| this._numScrollAttempts = 0; | |
| this._checkScrollPosition(); | |
| } | |
| readPosition(location) { | |
| return readState(this._getKey(location)); | |
| } | |
| _onScroll = () => { | |
| // It's possible that this scroll operation was triggered by what will be a | |
| // `POP` transition. Instead of updating the saved location immediately, we | |
| // have to enqueue the update, then potentially cancel it if we observe a | |
| // location update. | |
| if (this._savePositionHandle === null) { | |
| this._savePositionHandle = requestAnimationFrame(this._savePosition); | |
| } | |
| console.log('this._scrollTarget', this._scrollTarget) | |
| if (this._scrollTarget) { | |
| const [xTarget, yTarget] = this._scrollTarget; | |
| let x = false; | |
| let y = false; | |
| // Check if we are comparing to a element or window | |
| if (this._elTargetId) { | |
| const el = document.getElementById(this._elTargetId) | |
| x = el.scrollLeft | |
| y = el.scrollTop | |
| } else { | |
| x = scrollLeft(window); | |
| y = scrollTop(window); | |
| } | |
| if (x === xTarget && y === yTarget) { | |
| this._scrollTarget = null; | |
| this._cancelCheckScroll(); | |
| } | |
| } | |
| }; | |
| _savePosition = () => { | |
| this._savePositionHandle = null; | |
| // We have to directly update `DOMStateStorage`, because actually updating | |
| // the location could cause e.g. React Router to re-render the entire page, | |
| // which would lead to observably bad scroll performance. | |
| saveState( | |
| this._getKey(this._getCurrentLocation()), | |
| [scrollLeft(window), scrollTop(window)] | |
| ); | |
| }; | |
| _getKey(location) { | |
| // Use fallback key when actual key is unavailable. | |
| const key = location.key || this._history.createPath(location); | |
| return `${KEY_PREFIX}${key}`; | |
| } | |
| _cancelCheckScroll() { | |
| if (this._checkScrollHandle !== null) { | |
| requestAnimationFrame.cancel(this._checkScrollHandle); | |
| this._checkScrollHandle = null; | |
| } | |
| } | |
| _getDefaultScrollTarget() { | |
| const location = this._getCurrentLocation(); | |
| if (location.action === PUSH) { | |
| return [0, 0]; | |
| } | |
| return this.readPosition(location) || [0, 0]; | |
| } | |
| _checkScrollPosition = () => { | |
| this._checkScrollHandle = null; | |
| // We can only get here if scrollTarget is set. Every code path that unsets | |
| // scroll target also cancels the handle to avoid calling this handler. | |
| // Still, check anyway just in case. | |
| /* istanbul ignore if: paranoid guard */ | |
| if (!this._scrollTarget) { | |
| return; | |
| } | |
| const [x, y] = this._scrollTarget; | |
| // Check if we are scrolling a target or window | |
| if (this._elTargetId) { | |
| const el = document.getElementById(this._elTargetId) | |
| el.scrollLeft = x | |
| el.scrollTop = y | |
| } else { | |
| window.scrollTo(x, y); | |
| } | |
| ++this._numScrollAttempts; | |
| /* istanbul ignore if: paranoid guard */ | |
| if (this._numScrollAttempts >= MAX_SCROLL_ATTEMPTS) { | |
| this._scrollTarget = null; | |
| return; | |
| } | |
| this._checkScrollHandle = requestAnimationFrame(this._checkScrollPosition); | |
| }; | |
| } |
This file contains hidden or 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
| /* eslint-disable */ | |
| import React from 'react'; | |
| import ScrollBehavior from './scroll-behavior'; | |
| class ScrollBehaviorContainer extends React.Component { | |
| static propTypes = { | |
| shouldUpdateScroll: React.PropTypes.func, | |
| routerProps: React.PropTypes.object.isRequired, | |
| children: React.PropTypes.node.isRequired, | |
| elTarget: React.PropTypes.string | |
| }; | |
| componentDidMount() { | |
| const { routerProps, elTargetId } = this.props; | |
| this.scrollBehavior = new ScrollBehavior( | |
| routerProps.router, | |
| () => this.props.routerProps.location, | |
| elTargetId // pass a target | |
| ); | |
| this.onUpdate(null, routerProps); | |
| } | |
| componentDidUpdate(prevProps) { | |
| const { routerProps } = this.props; | |
| const prevRouterProps = prevProps.routerProps; | |
| if (routerProps.location === prevRouterProps.location) { | |
| return; | |
| } | |
| this.onUpdate(prevRouterProps, routerProps); | |
| } | |
| componentWillUnmount() { | |
| this.scrollBehavior.stop(); | |
| } | |
| onUpdate(prevRouterProps, routerProps) { | |
| const { shouldUpdateScroll } = this.props; | |
| let scrollPosition; | |
| if (!shouldUpdateScroll) { | |
| scrollPosition = true; | |
| } else { | |
| scrollPosition = shouldUpdateScroll.call( | |
| this.scrollBehavior, prevRouterProps, routerProps | |
| ); | |
| } | |
| this.scrollBehavior.updateScroll(scrollPosition); | |
| } | |
| render() { | |
| return this.props.children; | |
| } | |
| } | |
| export default function useScroll(shouldUpdateScroll, elTargetId) { | |
| return { | |
| renderRouterContext: (child, props) => ( | |
| <ScrollBehaviorContainer | |
| shouldUpdateScroll={shouldUpdateScroll} | |
| routerProps={props} | |
| elTargetId={elTargetId} | |
| > | |
| {child} | |
| </ScrollBehaviorContainer> | |
| ), | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment