Skip to content
This repository has been archived by the owner before Nov 9, 2022. It is now read-only.


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


Scroll Anchoring preserves the user's scroll position while DOM mutations change the page.

While dynamic adding, removing and updating elements on a page is useful, it is often jarring to the user to have contents shift while they are reading or attempting to click on a button. Scroll anchoring helps alleviate these user experience issues by locking the scroll position to an anchor node while page updates are performed.


$ npm install scroll-anchoring


The preserveAnchorNodePosition function is given a mutation callback to apply to the document. The scroll offset is recorded before and after applying the callback. Then the scroll position is adjusted to lock the anchor node in the same position. The helper will automatically try to detect the best anchor node in the current document.

import {preserveAnchorNodePosition} from 'scroll-anchoring'

const comments = document.getElementById('comments')
preserveAnchorNodePosition(document, () => {
  comments.insertAdjacentHTML('beforeend', newCommentHtml)

You can use your own anchor node if you don't want to rely on scroll anchoring's internal heuristic.

import {preservePosition} from 'scroll-anchoring'

const button = document.getElementById('button.expand-contents')
button.addEventListener('click', () => {
  const contents = document.getElementById('contents')

  // keep position locked to the button they just clicked after revealing the contents
  preservePosition(button, () => {
    contents.hidden = false


Circa 2012, I was implementing ajax and live updates for GitHub's Issue and PR discussion pages. So when you typed a comment and hit submit we'd dynamically insert it on the page without a full page refresh. In addition, any comments left by others would be inserted into the discussion thread in real time.

One usability annoyance with the initial concept was that inserting new comments and updating other elements would cause the user scroll position to unexpectedly jump up or down. You might be reading a comment while a new one came in and pushed it out the viewport. Or you'd be typing in the textarea and new comments would push the field out of the viewport. Pretty annoying.

The idea was to detect an anchor node on the screen that we thought the user would most care about. Then keep that node at the same viewport offsets after applying a page update. If you're actively typing in a field, pick that input or textarea as the anchor node. Or if you're scrolling around the page with a mouse over a comment while reading, we choose that element.

The affect is that the page appears to grow up or down depending on how your interacting with it as the update comes in.

While reading the comment timeline, new comments are appended to the bottom of the thread with no viewport adjusts.

But, while focused on the comment textarea, the comment timeline appears to grow upwards instead.

Browser support

  • Chrome
  • Firefox
  • Safari
  • Internet Explorer 11
  • Microsoft Edge


npm install
npm test

See Also


Distributed under the MIT license. See LICENSE for details.


Preserves the user's scroll position while DOM mutations change the page.







No packages published