Giter Club home page Giter Club logo

react-scroll-manager's Introduction

react-scroll-manager

Build Status npm version

Overview

In a single page application (SPA), the application manipulates the browser history and DOM to simulate navigation. Because navigation is simulated and rendering is dynamic, the usual browser behavior of restoring scroll position when navigating back and forth through the history is not generally functional. While some browsers (particularly Chrome) attempt to support automatic scroll restoration in response to history navigation and asynchronous page rendering, this support is still incomplete and inconsistent. Similarly, SPA router libraries provide varying but incomplete levels of scroll restoration. For example, the current version of React Router does not provide scroll management, and older versions did not provide support for all cases.

This library attempts to provide this missing functionality to React applications in a flexible and mostly router-agnostic way. It supports saving per-location window and element scroll positions to session storage and automatically restoring them during navigation. It also provides support for the related problem of navigating to hash links that reference dynamically rendered elements.

Requirements

This library has the following requirements:

The following features of newer browsers are supported with fallbacks for older browsers:

Installation

npm install react-scroll-manager

Example

The following example demonstrates usage of this library with React Router v4. It includes scroll restoration for both the main content window and a fixed navigation panel.

import React from 'react';
import { Router } from 'react-router-dom';
import { ScrollManager, WindowScroller, ElementScroller } from 'react-scroll-manager';
import { createBrowserHistory as createHistory } from 'history';

class App extends React.Component {
  constructor() {
    super();
    this.history = createHistory();
  }
  render() {
    return (
      <ScrollManager history={this.history}>
        <Router history={this.history}>
          <WindowScroller>
            <ElementScroller scrollKey="nav">
              <div className="nav">
                ...
              </div>
            </ElementScroller>
            <div className="content">
              ...
            </div>
          </WindowScroller>
        </Router>
      </ScrollManager>
    );
  }
}

API

ScrollManager

The ScrollManager component goes outside of your router component. It enables manual scroll restoration, reads and writes scroll positions from/to session storage, saves positions before navigation events, handles scrolling of nested components like WindowScroller and ElementScroller, and performs delayed scrolling to hash links anywhere within the document. It has the following properties:

Name Type Required Description
history object yes A history object, as returned by createBrowserHistory or createMemoryHistory.
sessionKey string no The key under which session state is stored. Defaults to ScrollManager.
timeout number no The maximum number of milliseconds to wait for rendering to complete. Defaults to 3000.

WindowScroller

The WindowScroller component goes immediately inside your router component. It handles scrolling the window position after navigation. If your window position never changes (e.g. your layout is fixed and all scrolling occurs within elements), it need not be used. It has no properties, but must be nested within a ScrollManager.

ElementScroller

The ElementScroller component goes immediately outside of a scrollable component (e.g. with overflow: auto style) for which you would like to save and restore the scroll position. It must be nested within a ScrollManager and has the following required property:

Name Type Required Description
scrollKey string yes The key within the session state under which the element scroll position is stored.

Tips

Use router link elements for hash links within a page

Always be sure to use your router library's link component rather than <a> tags when navigating to hash links. While a link like <a href="#id"> will navigate to the given element on the current page, it bypasses the usual call to history.pushState, which assigns a unique key to the history location. Without a location key, the library has no way to associate the position with the location, and scroll restoration won't work for those locations.

  <Link to="#id">...</Link> <!-- right way -->
  <a href="#id">...</a>     <!-- wrong way -->

Acknowledgments

License

react-scroll-manager is available under the ISC license.

react-scroll-manager's People

Contributors

soerenbf avatar trevorr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

react-scroll-manager's Issues

Partial success with react 18.2.0 and react-router 6.3.0

I have an album application https://oldalbum.bang.priv.no/

When navigating up from an album entry, I would like to scroll the album to the entry I'm navigating from.

I have achieved partial success:

  1. Navigation with doesn't position the album on the correct entry
  2. However, if I edit the hash and reload, I am scrolled to the right position

So something works...! :-)

What am I missing to make it work?
Or is the problem that the album hasn't been rendered when I try to do the scroll? (is that why editing the URL works?)
Or is the problem the router I'm using? (not one of react-router's own, but a router from redux-first-history 5.1.1)

The repo for the application is here: https://github.com/steinarb/oldalbum
This is a maven-built java application.
The react frontend is here: https://github.com/steinarb/oldalbum/tree/master/oldalbum.web.frontend/src/main/frontend

My experiments with ScrollManager has been pushed to a different repo (I use a dedicated scratch repo to avoid cluttering regular repos with my experiments): https://github.com/steinarb/scratch/commits/oldalbum/router-link-to-anchors

The changes are:

  1. Add an id attribute on the <div> of the album entries
  2. Add an hash matching the id attribute on the up to an album a picture or sub-album is part of
  3. Add a around the (editing the URL and navigating to a hash worked already here)
  4. Add an around each album entry using the id of the div as the scrollKey

Does this sound like something that should work? Or have I misread the docs?

does it work with react-router 5?

I'm getting this issue with CRA TypeScript project and "react-router-dom": "^5.0.1":

Type '{ children: Element; history: History<any>; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<BrowserRouter> & Readonly<{ children?: ReactNode; }> & Readonly<BrowserRouterProps>'.
  Property 'history' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<BrowserRouter> & Readonly<{ children?: ReactNode; }> & Readonly<BrowserRouterProps>'.  TS2322

     3 |     import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

    44 |     return (
    45 |       <ScrollManager history={this.history}>
  > 46 |         <Router history={this.history}>
       |          ^

Screen Shot 2019-09-26 at 00 39 07

`WindowScroller` wrapper prevents react-hot-loader from atomic updates

Hey,

As soon as I add <WindowScroller> wrapper, react-hot-loader can't atomically refresh updates to JSX code anymore and a full app reload (not the browser, but the hot(App)) is fired.

Works great when I remove the <WindowScroller> container wrapper.

i.e:

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { createBrowserHistory as createHistory } from 'history';
import { ScrollManager, WindowScroller } from 'react-scroll-manager';
import App from 'ui/containers/App';
import { hot } from 'react-hot-loader/root';
import { PusherProviderContainer } from 'ui/containers/PusherProvider';
import { IntlProvider } from 'react-intl';

class RootContainer extends Component {
    static propTypes = {
        store: PropTypes.object.isRequired,
        pusher: PropTypes.object.isRequired,
    };


    constructor(props, context) {
        super(props, context);

        this.history = createHistory({ basename: "/app" });
    }

    render() {
        const {store} = this.props;

        return (
            <Provider store={store}>
                <IntlProvider locale="fr">
                    <ScrollManager history={this.history}>
                        <Router history={this.history}>
                            <WindowScroller> {/* Prevents react-hot-reload atomic updates */}
                                <PusherProviderContainer pusher={this.props.pusher}>
                                    <App/>
                                </PusherProviderContainer>
                            </WindowScroller>
                        </Router>
                    </ScrollManager>
                </IntlProvider>
            </Provider>
        );
    }
}

export default hot(RootContainer);

Any Idea?

React-redux-router reducer change causes elementScroller to restore node

I have a page in my site that has an element scroller attached to it. Everything works great and when the page refreshes or navigates, everything works exactly as it should.

However, on my page, if I do a call to update a reducer, it causes an app wide state change.

In ElementScroller.js on line 19, there is a componentDidUpdate function for the element.

componentDidUpdate(prevProps) { this._unregister(prevProps); this._register(); }

As a result, when the reducer updates, this code is called and causes the element's scroll position to be reset. So if the page "started" at the top, and I scroll down the page and do something to update a reducer, the above code will cause the element to scroll to the top of the page.

I commented out the did update and the problem went away, but I am not sure if this will have any side effects elsewhere.

Is there anyway it can be updated so that it doesn't restore the node scroll position on did update due to a reducer change?

First page load with hash doesn't work

I guess the history.listen event isn't fired on load, but only successive navigations? Anyway, maybe we could check for location.hash on the initial restoreWindow call on ScrollManager?

I'll see if I can get around to submitting a PR, and you can have a look @trevorr ?

Does not appear to work with React Router v5.

I tried several configurations. No errors, but basically functions as if there was no scroll restoration at all.

I had some luck reverting to v4, but still wasn't totally working. I can come back and fill in more details as time allows.

When page was resized (the height has become smaller, eg: lazy load items or lanscape mod on mobile phone), will scroll hundreds of times before timeout

I had problem when page with button "more" increased height till 5000 - 10 000 pixed, you clicked to some Item at bottom (scrollTop === 10 000), and you press "back", height of page becomes original (~1 000px) without new items (because button more was not clicked again). In this case, react scroll manager going to scoll hundreds of times (on any observer event) but unsuccessful, because it scroll to 1 000px and 10 000px is unreachable. At this time user try to scroll UP, he will be scrolled to bottom now, till timeout. A very nasty bug.

In this regard, I changed the logic of the library's behavior. Now, before attempting to scroll the page, we calculate whether this attempt will be successful, if it cannot succeed - it don't even try to do this.

But now I saw situations, when page has a little bit dynamic height (+- 5px because of example: :hover { border: 2px solid red; }), for this case Ive added new prop blockSizeTolerance`

I've removed Skipping save due to deferred scroll of ${scrollKey} bacause no need it anymore, _savePosition will not have fake calls from attemptScroll because now we execute window.scrollTo only once, when 100% have reachable scroll position.

Pull: #15

Can't add ref to ElementScroll child

ElementScroller overwrites the existing ref property on its child if one exists, making it difficult to reference that child element elsewhere. It would be helpful if ElementScroller merged its ref with the child's ref, or otherwise forwarded a ref to the child.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.