54 lines
1.7 KiB
JavaScript
54 lines
1.7 KiB
JavaScript
export default function preserveScroll (callback) {
|
|
// preserve the actual scroll position
|
|
const scrollTop = window.scrollY
|
|
|
|
// if the scroll position is at the top, we don't need to preserve it, just call the callback
|
|
if (scrollTop <= 0) {
|
|
callback()
|
|
return
|
|
}
|
|
|
|
// get a reference element at the center of the viewport to track if content is added above it
|
|
const ref = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2)
|
|
const refTop = ref ? ref.getBoundingClientRect().top + scrollTop : scrollTop
|
|
|
|
// observe the document for changes in height
|
|
const observer = new window.MutationObserver(() => {
|
|
// request animation frame to ensure the DOM is updated
|
|
window.requestAnimationFrame(() => {
|
|
// we can't proceed if we couldn't find a traceable reference element
|
|
if (!ref) {
|
|
cleanup()
|
|
return
|
|
}
|
|
|
|
// get the new position of the reference element along with the new scroll position
|
|
const newRefTop = ref ? ref.getBoundingClientRect().top + window.scrollY : window.scrollY
|
|
// has the reference element moved?
|
|
const refMoved = newRefTop - refTop
|
|
|
|
// if the reference element moved, we need to scroll to the new position
|
|
if (refMoved > 0) {
|
|
window.scrollTo({
|
|
// some browsers don't respond well to fractional scroll position, so we round up the new position to the nearest integer
|
|
top: scrollTop + Math.ceil(refMoved),
|
|
behavior: 'instant'
|
|
})
|
|
}
|
|
|
|
cleanup()
|
|
})
|
|
})
|
|
|
|
const timeout = setTimeout(() => cleanup(), 1000) // fallback
|
|
|
|
function cleanup () {
|
|
clearTimeout(timeout)
|
|
observer.disconnect()
|
|
}
|
|
|
|
observer.observe(document.body, { childList: true, subtree: true })
|
|
|
|
callback()
|
|
}
|