| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- 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;
|