export function scrollTo(element: HTMLElement, target: number, duration: number) {
target = Math.round(target)
duration = Math.round(duration)
if (duration < 0) {
return Promise.reject("bad duration")
}
if (duration === 0) {
element.scrollTop = target
return Promise.resolve()
}
const startTime = Date.now()
const endTime = startTime + duration
const startTop = element.scrollTop
const distance = target - startTop
// based on http://en.wikipedia.org/wiki/Smoothstep
const smoothStep = (start: number, end: number, point: number) => {
if (point <= start) { return 0 }
if (point >= end) { return 1 }
const x = (point - start) / (end - start) // interpolation
return x * x * (3 - 2 * x)
}
return new Promise((resolve, reject) => {
// This is to keep track of where the element's scrollTop is
// supposed to be, based on what we're doing
let previousTop = element.scrollTop
// This is like a think function from a game loop
const scrollFrame = () => {
if (element.scrollTop !== previousTop) {
return
}
// set the scrollTop for this frame
const now = Date.now()
const point = smoothStep(startTime, endTime, now)
const frameTop = Math.round(startTop + (distance * point))
element.scrollTop = frameTop
// check if we're done!
if (now >= endTime) {
resolve()
return
}
// If we were supposed to scroll but didn't, then we
// probably hit the limit, so consider it done not
// interrupted.
if (element.scrollTop === previousTop
&& element.scrollTop !== frameTop) {
resolve()
return
}
previousTop = element.scrollTop
// schedule next frame for execution
setTimeout(scrollFrame, 0)
}
// boostrap the animation process
setTimeout(scrollFrame, 0)
})
}