UNPKG

stroll.js

Version:

stroll.js is strolling along in your viewport

322 lines (252 loc) 9.86 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.stroll = factory()); }(this, (function () { 'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; function createOptions(defaults, options, adapter) { return Object.assign({ ignoreUserScroll: false, allowInvalidPositions: false, offset: { x: 0, y: 0 }, duration: 500, focus: true, // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/ easing: function easing(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; } }, defaults, options, { start: adapter.getCurrentPosition() }); } function resolveDuration(duration, distance) { var type = typeof duration === 'undefined' ? 'undefined' : _typeof(duration); if (type === 'number') return duration; if (type === 'function') return duration(distance); var parsed = parseFloat(duration); if (isNaN(parsed)) { throw new Error('invalid duration'); } return parsed; } function resolveOffset(offset, adapter) { var type = typeof offset === 'undefined' ? 'undefined' : _typeof(offset); if (type === 'number') { return adapter.createPositionFromNumber(offset); } if (type === 'object') { if (typeof offset.x === 'undefined') offset.x = 0; if (typeof offset.y === 'undefined') offset.y = 0; return offset; } throw new Error('invalid offset'); } function positionAbsolute(distance) { return { x: Math.abs(distance.x), y: Math.abs(distance.y) }; } function positionCompare(pos1, pos2) { return pos1.x === pos2.x && pos1.y === pos2.y; } function positionAdd(pos1, pos2) { return { x: pos1.x + pos2.x, y: pos1.y + pos2.y }; } function ease(easing, timeElapsed, start, target, duration) { return { x: Math.round(easing(timeElapsed, start.x, target.x, duration)), y: Math.round(easing(timeElapsed, start.y, target.y, duration)) }; } function createLoop(stroll, options) { var adapter = options.adapter; function startLoop(resolve) { var animationFrame = void 0; var timeStart = void 0; var lastPosition = void 0; function loop(timeCurrent) { if (!timeStart) { timeStart = timeCurrent; } var timeElapsed = timeCurrent - timeStart; var newPosition = ease(options.easing, timeElapsed, options.start, options.target, options.duration); if (!options.allowInvalidPositions && adapter.isPositionOutsideOfElement(newPosition)) { return stroll.currentStroll('invalid_position'); } if (!options.ignoreUserScroll && lastPosition && !positionCompare(lastPosition, adapter.getCurrentPosition())) { return stroll.currentStroll('user_scrolled'); } if (timeElapsed > options.duration) { done(resolve); } else { adapter.scrollTo(newPosition); lastPosition = adapter.getCurrentPosition(); next(); } } function next() { animationFrame = window.requestAnimationFrame(loop); } if (stroll.currentStroll) { stroll.currentStroll('new_stroll'); } next(); stroll.currentStroll = function (cancelReason) { window.cancelAnimationFrame(animationFrame); resolve({ wasCancelled: true, cancelReason: cancelReason }); }; return stroll.currentStroll; } function done(resolve) { stroll.currentStroll = null; // easing might create small offsets from the requested target adapter.scrollTo(positionAdd(options.start, options.target)); // keyboard navigation might reset the scroll position // this sets focus to the element to prevent such problems if (options.element && options.focus) { if (!options.element.hasAttribute('tabindex')) { options.element.setAttribute('tabindex', '-1'); } options.element.focus(); } resolve({ wasCancelled: false }); } return { start: startLoop }; } function createStroller(adapter) { function stroll(target) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return new Promise(function (resolve) { var strollOptions = createOptions(stroll.DEFAULTS, options, adapter); var offset = resolveOffset(strollOptions.offset, adapter); var strollTarget = adapter.resolveTarget(target, strollOptions.start, offset); var duration = resolveDuration(strollOptions.duration, positionAbsolute(strollTarget.target)); var loop = createLoop(stroll, Object.assign({}, strollOptions, strollTarget, { duration: duration, offset: offset, adapter: adapter })); loop.start(resolve); }); } stroll.currentStroll = null; stroll.relative = function (offset) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return stroll(null, Object.assign({ offset: offset }, options)); }; stroll.DEFAULTS = {}; return stroll; } var _typeof$1 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var d = typeof document !== 'undefined' ? document : {}; var w = typeof window !== 'undefined' ? window : {}; function resolveTargetFactory(el, createPositionFromNumber) { return function resolveTarget(userTarget, start, offset) { var type = typeof userTarget === 'undefined' ? 'undefined' : _typeof$1(userTarget); var target = type === 'string' ? el.querySelector(userTarget) : userTarget; var result = {}; if (userTarget === null || type === 'undefined') { var zero = { x: 0, y: 0 }; return resolveTarget(offset, zero, zero); } if (type === 'number') { var pos = createPositionFromNumber(userTarget); // add start and offset to axes, that are not 0 by default pos.x = pos.x - start.x + offset.x; pos.y = pos.y - start.y + offset.y; result.target = pos; result.focus = false; return result; } if (type === 'object' && !(userTarget instanceof w.HTMLElement)) { result.target = { x: (userTarget.x || 0) - start.x + offset.x, y: (userTarget.y || 0) - start.y + offset.y }; result.focus = false; return result; } if (!target || !target.getBoundingClientRect) { throw new Error('invalid target'); } var boundingRect = target.getBoundingClientRect(); result.element = target; result.target = { x: boundingRect.left + offset.x, y: boundingRect.top + offset.y }; return result; }; } function createPositionFromNumberFactory(calculateMaxima) { return function (number) { var maxima = calculateMaxima(); var primaryAxis = maxima.y > maxima.x ? 'y' : 'x'; var result = {}; result[primaryAxis] = number; result[primaryAxis === 'x' ? 'y' : 'x'] = 0; return result; }; } function isPositionOutsideOfElementFactory(calculateMaxima) { return function (pos) { if ((typeof pos === 'undefined' ? 'undefined' : _typeof$1(pos)) !== 'object') return true; var maxima = calculateMaxima(); return pos.x < 0 || pos.x > maxima.x || pos.y < 0 || pos.y > maxima.y; }; } function createBaseAdapter(el, calculateMaxima) { var createPositionFromNumber = createPositionFromNumberFactory(calculateMaxima); var isPositionOutsideOfElement = isPositionOutsideOfElementFactory(calculateMaxima); var resolveTarget = resolveTargetFactory(el, createPositionFromNumber); return { createPositionFromNumber: createPositionFromNumber, resolveTarget: resolveTarget, isPositionOutsideOfElement: isPositionOutsideOfElement }; } function createWindowAdapter() { function calculateMaxima() { var documentWidth = Math.max(d.body.scrollWidth, d.body.offsetWidth, d.documentElement.clientWidth, d.documentElement.scrollWidth, d.documentElement.offsetWidth) - w.innerWidth; var x = Math.max(0, documentWidth); var documentHeight = Math.max(d.body.scrollHeight, d.body.offsetHeight, d.documentElement.clientHeight, d.documentElement.scrollHeight, d.documentElement.offsetHeight) - w.innerHeight; var y = Math.max(0, documentHeight); return { x: x, y: y }; } return Object.assign(createBaseAdapter(d, calculateMaxima), { getCurrentPosition: function getCurrentPosition() { return { x: w.scrollX || w.pageXOffset, y: w.scrollY || w.pageYOffset }; }, scrollTo: function scrollTo(pos) { w.scrollTo(pos.x, pos.y); } }); } function createElementAdapter(el) { function calculateMaxima() { return { x: Math.max(0, el.scrollWidth - el.clientWidth), y: Math.max(0, el.scrollHeight - el.clientHeight) }; } return Object.assign(createBaseAdapter(el, calculateMaxima), { getCurrentPosition: function getCurrentPosition() { return { x: el.scrollLeft, y: el.scrollTop }; }, scrollTo: function scrollTo(pos) { el.scrollLeft = pos.x; el.scrollTop = pos.y; } }); } var publicAdapters = { element: createElementAdapter }; var stroll = createStroller(createWindowAdapter()); stroll.factory = function (el) { var adapter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'element'; if (typeof adapter === 'string') { adapter = publicAdapters[adapter]; } return createStroller(adapter(el)); }; return stroll; })));