Last active
June 13, 2023 10:37
-
-
Save guillermodlpa/b31c7a00ce50465bb44b034e7231d794 to your computer and use it in GitHub Desktop.
smooth scroll function. native scrollTo or scrollIntoView conflict with each other if there are different horizontal and vertical scrolls happening
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Inspired by https://codepen.io/oxleberry/pen/BOEBaB | |
* | |
* With added support for horizontal scrolling, scrolling the window, and linear easing | |
* | |
* native scrollTo or scrollIntoView conflict with each other if there are different horizontal and vertical scrolls happening | |
*/ | |
// Easing equations, http://www.gizma.com/easing/ | |
function easeOutCubic(t: number, b: number, c: number, d: number) { | |
t /= d; | |
t--; | |
return c * (t * t * t + 1) + b; | |
} | |
// function linear(t: number, b: number, c: number, d: number) { | |
// return (c * t) / d + b; | |
// } | |
type SmoothScrollToOptions = { | |
targetPos: number; | |
horizontal?: boolean; | |
duration?: number; | |
getOffsetParent: () => Element | null; | |
}; | |
export function smoothScrollToPosition({ | |
targetPos, | |
horizontal = false, | |
duration = 250, | |
getOffsetParent, | |
}: SmoothScrollToOptions) { | |
let offsetParent = getOffsetParent(); | |
if (!offsetParent) return; | |
if (offsetParent.tagName === 'BODY') { | |
offsetParent = document.getElementsByTagName('html')[0]; | |
if (!offsetParent) return; | |
} | |
// tracks the current X position in pixels | |
const currentPos = horizontal | |
? offsetParent.scrollLeft | |
: offsetParent.scrollTop; | |
// tracks the remaining distance from target in pixels | |
const distance = targetPos - currentPos; | |
// track the time, for use with request animation | |
let start: null | number = null; | |
let animationId: number | undefined = undefined; | |
const animation = (timestamp: number) => { | |
// timestamp part of reqAF to keep track of animation time | |
if (!start) start = timestamp; | |
// tracks the time elapsed | |
const timeElapsed = timestamp - start; | |
// run, calculates how to reach targetPos in an ease trajectory | |
// 1) value of current time in the animation | |
// 2) currentPos position in pixel | |
// 3) how far we need to go till we reach targetPos in pixels | |
// 4) target end time of animation | |
const run = easeOutCubic(timeElapsed, currentPos, distance, duration); | |
// scrollTo, first argument scrolls on the x axis | |
// scrollTo, second argument scrolls on the y axis | |
// animates till we reach the duration time | |
offsetParent?.scrollTo(horizontal ? { left: run } : { top: run }); | |
if (timeElapsed < duration) { | |
requestAnimationFrame(animation); | |
} else if (animationId) { | |
cancelAnimationFrame(animationId); | |
} | |
}; | |
// recursively renders the animation function | |
animationId = requestAnimationFrame(animation); | |
} | |
export type SmoothScrollOptions = { | |
horizontal?: boolean; | |
duration?: number; | |
getTargetPos?: (target: HTMLElement, offsetParent: Element) => number; | |
getOffsetParent?: (target: HTMLElement) => Element | null; | |
}; | |
export function smoothScrollToTarget( | |
target: HTMLElement, | |
{ | |
horizontal = false, | |
duration = 250, | |
getTargetPos, | |
getOffsetParent, | |
}: SmoothScrollOptions = {}, | |
) { | |
let offsetParent = getOffsetParent | |
? getOffsetParent(target) | |
: target.offsetParent; | |
if (!offsetParent) return; | |
if (offsetParent.tagName === 'BODY') { | |
offsetParent = document.getElementsByTagName('html')[0]; | |
if (!offsetParent) return; | |
} | |
// tracks the target X positiom in pixels | |
const targetPos = getTargetPos | |
? getTargetPos(target, offsetParent) | |
: horizontal | |
? target.offsetLeft | |
: target.offsetTop; | |
return smoothScrollToPosition({ | |
targetPos, | |
horizontal, | |
duration, | |
getOffsetParent: () => offsetParent, | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment