import BezierEasing from 'bezier-easing' let _ = { $ (selector) { return document.querySelector(selector) }, on (element, events, handler) { if (!(events instanceof Array)) { events = [events] } for (let i = 0; i < events.length; i++) { element.addEventListener(events[i], handler) } }, off (element, events, handler) { if (!(events instanceof Array)) { events = [events] } for (let i = 0; i < events.length; i++) { element.removeEventListener(events[i], handler) } } } let eases = { ease: [0.25, 0.1, 0.25, 1.0], linear: [0.00, 0.0, 1.00, 1.0], 'ease-in': [0.42, 0.0, 1.00, 1.0], 'ease-out': [0.00, 0.0, 0.58, 1.0], 'ease-in-out': [0.42, 0.0, 0.58, 1.0] } export const scrollTo = (element, duration = 500, options) => { options = options || {} options.easing = eases[options.easing || 'ease'] if (typeof element === 'string') { element = _.$(element) } let page = _.$('html, body') let events = [ 'scroll', 'mousedown', 'wheel', 'DOMMouseScroll', 'mousewheel', 'keyup', 'touchmove' ] let abort = false let abortFn = () => { abort = true } _.on(page, events, abortFn) let initialY = window.pageYOffset let elementY = 0 if (Object.is(typeof element, 'number')) { elementY = element } else { elementY = initialY + element.getBoundingClientRect().top + (options.offset || 0) } let targetY = document.body.scrollHeight - elementY < window.innerHeight ? document.body.scrollHeight - window.innerHeight : elementY if (options.offset) { targetY += options.offset } let diff = targetY - initialY let easing = BezierEasing.apply(BezierEasing, options.easing) let start let done = () => { _.off(page, events, abortFn) if (abort && options.onCancel) options.onCancel() if (!abort && options.onDone) options.onDone() } if (!diff) return window.requestAnimationFrame(function step (timestamp) { if (abort) return done() if (!start) start = timestamp let time = timestamp - start let progress = Math.min(time / duration, 1) progress = easing(progress) window.scrollTo(0, initialY + diff * progress) if (time < duration) { window.requestAnimationFrame(step) } else { done() } }) }