
srestore-scroll is a JavaScript scroll restoration library that saves and restores scroll positions for overflowing elements and the window using history.state.
It works by tracking scroll positions in the browser’s history state rather than sessionStorage. This means it can handle multiple scroll states per URL, a critical feature for SPA (single-page applications) where the same URL might show different content at different times.
Features
- Vanilla JavaScript with zero dependencies.
- Works with any scrollable container element, not just the window.
- Provides event hooks for store and restore operations.
- Debug mode for troubleshooting scroll restoration issues.
- Lightweight at just a few KB when minified.
How to use it:
1. Install restoreScroll and import it into your project.
# NPM $ npm install restoreScroll
import { restoreScroll } from "@hirasso/restore-scroll";<!-- OR -->
<script type="module">
import { restoreScroll } from "./index.js";
</script>2. Call restoreScroll on the target scrollable element. The example here is to select all scrollable containers and apply it to each one.
document.querySelectorAll(".scroll1, .scroll2, .scroll3")
.forEach((el) => restoreScroll(el,{
// options here
}));3. If you want to manage the main page’s scroll position, you first need to tell the browser you’re handling it yourself. Then, you can apply the library to the window object.
window.history.scrollRestoration = “manual”;
restoreScroll(window);
4. Available options:
debug(boolean): Set totrueto see debug logs in the console. Defaults tofalse.events(object): An object withstoreandrestorecallback functions.
document.querySelectorAll(".scroll1, .scroll2, .scroll3")
.forEach((el) => restoreScroll(el,{
debug: false,
events: {
store: (el, event) => console.log("Position stored for", el),
restore: (el, event) => console.log("Position restored for", el),
},
}));5. You can also attach standard DOM event listeners to the element. The events are prefixed with restore-scroll::
const el = document.querySelector("#scroller");
el.addEventListener("restore-scroll:restore", (e) => {
// The position is in e.detail.position
console.log(e.detail.position);
});
restoreScroll(el);FAQs:
Q: Why not just use the browser’s built-in scroll restoration?
A: The browser’s native scroll restoration only works for the window itself, not for individual scrollable containers. It also doesn’t handle SPA navigation patterns well where the same URL might show different content.
Q: Does this work with single-page applications (SPAs)?
A: Yes, it’s designed specifically for them. Since it uses the History API, it integrates naturally with the client-side routing common in SPAs built with Vue, Svelte, or vanilla JavaScript.
Q: Why do I need to set history.scrollRestoration to manual?
A: You must set history.scrollRestoration = "manual" to prevent the browser’s default scroll handling from interfering. The browser’s “auto” setting will try to restore the scroll position on its own, which can lead to conflicts or unpredictable behavior when you’re also using this library to do the same thing.
Q: What happens if the element I’m tracking doesn’t exist on the next page?
A: Nothing bad happens. The library stores the selector and position in the history state. If it can’t find an element matching that selector on the page, it simply does nothing. It won’t throw an error.
Q: How does it perform with many scrollable elements on one page?
A: Performance is quite good. The scroll event handler is debounced, meaning it only executes after the user has stopped scrolling for a moment. This prevents the history.replaceState call from happening hundreds of times as the user scrolls, which keeps the main thread free.
Q: Can I stop the scroll from being restored in certain situations?
A: Yes. You can attach a listener to the restore-scroll:restore event and call event.preventDefault() within it.
Changelog:
v1.0.0 (01/03/2026)
- Switch to @medv/finder for the automatic selector generation
- Return a boolean from restoreScroll() indicating success / failure







