mirror of
https://github.com/gedoor/legado.git
synced 2025-08-10 00:52:30 +00:00
187 lines
4.7 KiB
JavaScript
187 lines
4.7 KiB
JavaScript
const easeInOutQuad = (t, b, c, d) => {
|
|
t /= d / 2
|
|
if (t < 1) return (c / 2) * t * t + b
|
|
t--
|
|
return (-c / 2) * (t * (t - 2) - 1) + b
|
|
}
|
|
|
|
const jumper = () => {
|
|
// private variable cache
|
|
// no variables are created during a jump, preventing memory leaks
|
|
|
|
let container // container element to be scrolled (node)
|
|
let element // element to scroll to (node)
|
|
|
|
let start // where scroll starts (px)
|
|
let stop // where scroll stops (px)
|
|
|
|
let offset // adjustment from the stop position (px)
|
|
let easing // easing function (function)
|
|
let a11y // accessibility support flag (boolean)
|
|
|
|
let distance // distance of scroll (px)
|
|
let duration // scroll duration (ms)
|
|
|
|
let timeStart // time scroll started (ms)
|
|
let timeElapsed // time spent scrolling thus far (ms)
|
|
|
|
let next // next scroll position (px)
|
|
|
|
let callback // to call when done scrolling (function)
|
|
|
|
// scroll position helper
|
|
|
|
function location() {
|
|
let top = container.scrollTop || container.scrollY || container.pageYOffset
|
|
top = typeof top === 'undefined' ? 0 : top
|
|
return top
|
|
}
|
|
|
|
// element offset helper
|
|
|
|
function top(element) {
|
|
const elementTop = element.getBoundingClientRect().top
|
|
const containerTop = container.getBoundingClientRect
|
|
? container.getBoundingClientRect().top
|
|
: 0
|
|
|
|
return elementTop - containerTop + start
|
|
}
|
|
|
|
// scrollTo helper
|
|
|
|
function scrollTo(top) {
|
|
container.scrollTo
|
|
? container.scrollTo(0, top) // window
|
|
: (container.scrollTop = top) // custom container
|
|
}
|
|
|
|
// rAF loop helper
|
|
|
|
function loop(timeCurrent) {
|
|
// store time scroll started, if not started already
|
|
if (!timeStart) {
|
|
timeStart = timeCurrent
|
|
}
|
|
|
|
// determine time spent scrolling so far
|
|
timeElapsed = timeCurrent - timeStart
|
|
|
|
// calculate next scroll position
|
|
next = easing(timeElapsed, start, distance, duration)
|
|
|
|
// scroll to it
|
|
scrollTo(next)
|
|
|
|
// check progress
|
|
timeElapsed < duration
|
|
? requestAnimationFrame(loop) // continue scroll loop
|
|
: done() // scrolling is done
|
|
}
|
|
|
|
// scroll finished helper
|
|
|
|
function done() {
|
|
// account for rAF time rounding inaccuracies
|
|
scrollTo(start + distance)
|
|
|
|
// if scrolling to an element, and accessibility is enabled
|
|
if (element && a11y) {
|
|
// add tabindex indicating programmatic focus
|
|
element.setAttribute('tabindex', '-1')
|
|
|
|
// focus the element
|
|
element.focus()
|
|
}
|
|
|
|
// if it exists, fire the callback
|
|
if (typeof callback === 'function') {
|
|
callback()
|
|
}
|
|
|
|
// reset time for next jump
|
|
timeStart = false
|
|
}
|
|
|
|
// API
|
|
|
|
function jump(target, options = {}) {
|
|
// resolve options, or use defaults
|
|
duration = options.duration || 1000
|
|
offset = options.offset || 0
|
|
callback = options.callback // "undefined" is a suitable default, and won't be called
|
|
easing = options.easing || easeInOutQuad
|
|
a11y = options.a11y || false
|
|
|
|
// resolve container
|
|
switch (typeof options.container) {
|
|
case 'object':
|
|
// we assume container is an HTML element (Node)
|
|
container = options.container
|
|
break
|
|
|
|
case 'string':
|
|
container = document.querySelector(options.container)
|
|
break
|
|
|
|
default:
|
|
container = window
|
|
}
|
|
|
|
// cache starting position
|
|
start = location()
|
|
|
|
// resolve target
|
|
switch (typeof target) {
|
|
// scroll from current position
|
|
case 'number':
|
|
element = undefined // no element to scroll to
|
|
a11y = false // make sure accessibility is off
|
|
stop = start + target
|
|
break
|
|
|
|
// scroll to element (node)
|
|
// bounding rect is relative to the viewport
|
|
case 'object':
|
|
element = target
|
|
stop = top(element)
|
|
break
|
|
|
|
// scroll to element (selector)
|
|
// bounding rect is relative to the viewport
|
|
case 'string':
|
|
element = document.querySelector(target)
|
|
stop = top(element)
|
|
break
|
|
}
|
|
|
|
// resolve scroll distance, accounting for offset
|
|
distance = stop - start + offset
|
|
|
|
// resolve duration
|
|
switch (typeof options.duration) {
|
|
// number in ms
|
|
case 'number':
|
|
duration = options.duration
|
|
break
|
|
|
|
// function passed the distance of the scroll
|
|
case 'function':
|
|
duration = options.duration(distance)
|
|
break
|
|
}
|
|
|
|
// start the loop
|
|
requestAnimationFrame(loop)
|
|
}
|
|
|
|
// expose only the jump method
|
|
return jump
|
|
}
|
|
|
|
// export singleton
|
|
|
|
const singleton = jumper()
|
|
|
|
export default singleton
|