├── .github └── workflows │ └── release.yml ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src └── index.tsx └── tsconfig.json /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: [push, pull_request] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.ref }} 6 | cancel-in-progress: true 7 | 8 | permissions: 9 | contents: write # to be able to publish a GitHub release 10 | id-token: write # to enable use of OIDC for npm provenance 11 | issues: write # to be able to comment on released issues 12 | pull-requests: write # to be able to comment on released pull requests 13 | 14 | jobs: 15 | release: 16 | name: 🚀 Release 17 | runs-on: ubuntu-latest 18 | if: 19 | ${{ github.repository == 'epicweb-dev/restore-scroll' && 20 | contains('refs/heads/main,refs/heads/beta,refs/heads/next,refs/heads/alpha', 21 | github.ref) && github.event_name == 'push' }} 22 | steps: 23 | - name: ⬇️ Checkout repo 24 | uses: actions/checkout@v4 25 | 26 | - name: ⎔ Setup node 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: 20 30 | 31 | - name: 📥 Download deps 32 | uses: bahmutov/npm-install@v1 33 | with: 34 | useLockFile: false 35 | 36 | - name: 📦 Run Build 37 | run: npm run build 38 | 39 | - name: 🚀 Release 40 | uses: cycjimmy/semantic-release-action@v4 41 | with: 42 | semantic_version: 17 43 | branches: | 44 | [ 45 | '+([0-9])?(.{+([0-9]),x}).x', 46 | 'main', 47 | 'next', 48 | 'next-major', 49 | {name: 'beta', prerelease: true}, 50 | {name: 'alpha', prerelease: true} 51 | ] 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | NPM_CONFIG_PROVENANCE: true 55 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | tsconfig.tsbuildinfo 4 | coverage 5 | /test-results/ 6 | /playwright-report/ 7 | /blob-report/ 8 | /playwright/.cache/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

🌀 @epic-web/restore-scroll

3 | 4 | Restore scroll position of elements on page navigation 5 | 6 |

7 | The <body> isn't the only thing that scrolls. When the user scrolls a list, then navigates back and forth, you may want to keep their scroll position where it was when they left. This library makes that easy. 8 |

9 |
10 | 11 | ``` 12 | npm install @epic-web/restore-scroll 13 | ``` 14 | 15 |
16 | 20 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | [![Build Status][build-badge]][build] 31 | [![MIT License][license-badge]][license] 32 | [![Code of Conduct][coc-badge]][coc] 33 | 34 | 35 | ## The Problem 36 | 37 | When a user navigates to a new page, the browser will scroll the page to the 38 | position it was at when the user left the page. This is a great feature, but 39 | it's not perfect. The browser only scrolls the `` element. If the user 40 | scrolls a list, then navigates back and forth, the browser will scroll the page 41 | to the top, but the list will still be scrolled to the position it was at when 42 | the user left the page. 43 | 44 | ## The Solution 45 | 46 | This library provides a way to restore the scroll position of any element on the 47 | page you choose. It does this by storing the scroll position of the element in 48 | session storage and then restoring it when the user navigates back to the page 49 | (very similar to how Remix handles scroll restoration for the ``). 50 | 51 | This depends on React Router's `useNavigation` and `useLocation` hooks. It could 52 | probably be generalized to work with other routers. PRs welcome. 53 | 54 | ## Demo 55 | 56 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/remix-run-remix-fczany?file=app%2Froutes%2F_index.tsx) 57 | 58 | Note: this demo is based on Remix, but will work with React Router as well. 59 | 60 | ## Usage 61 | 62 | ```tsx 63 | import { ElementScrollRestoration } from '@epic-web/restore-scroll' 64 | 65 | return ( 66 |
67 | 73 | 74 |
75 | ) 76 | ``` 77 | 78 | And that's it! Now when the user navigates away from the page and then back to 79 | it, the list will be scrolled to the position it was at when the user left the 80 | page. 81 | 82 | You can also specify horizontal scroll for elements like carousels: 83 | 84 | ```tsx 85 | 89 | ``` 90 | 91 | ## Tips: 92 | 93 | 1. This requires an inline script, so you'll need to pass a `nonce` if you're 94 | using a Content Security Policy that requires this. 95 | 2. Make certain to place the `ElementScrollRestoration` component _after_ the 96 | element you want to restore the scroll position of. This is because the 97 | component will render a `