123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790 |
- //
- // SmoothScroll for websites v1.4.10 (Balazs Galambosi)
- // http://www.smoothscroll.net/
- //
- // Licensed under the terms of the MIT license.
- //
- // You may use it in your theme if you credit me.
- // It is also free to use on any individual website.
- //
- // Exception:
- // The only restriction is to not publish any
- // extension for browsers or native application
- // without getting a written permission first.
- //
- (function () {
- // Scroll Variables (tweakable)
- var defaultOptions = {
- // Scrolling Core
- frameRate : 150, // [Hz]
- animationTime : 400, // [ms]
- stepSize : 100, // [px]
- // Pulse (less tweakable)
- // ratio of "tail" to "acceleration"
- pulseAlgorithm : true,
- pulseScale : 4,
- pulseNormalize : 1,
- // Acceleration
- accelerationDelta : 50, // 50
- accelerationMax : 3, // 3
- // Keyboard Settings
- keyboardSupport : true, // option
- arrowScroll : 50, // [px]
- // Other
- fixedBackground : true,
- excluded : ''
- };
- var options = defaultOptions;
- // Other Variables
- var isExcluded = false;
- var isFrame = false;
- var direction = { x: 0, y: 0 };
- var initDone = false;
- var root = document.documentElement;
- var activeElement;
- var observer;
- var refreshSize;
- var deltaBuffer = [];
- var deltaBufferTimer;
- var isMac = /^Mac/.test(navigator.platform);
- var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32,
- pageup: 33, pagedown: 34, end: 35, home: 36 };
- var arrowKeys = { 37: 1, 38: 1, 39: 1, 40: 1 };
- /***********************************************
- * INITIALIZE
- ***********************************************/
- /**
- * Tests if smooth scrolling is allowed. Shuts down everything if not.
- */
- function initTest() {
- if (options.keyboardSupport) {
- addEvent('keydown', keydown);
- }
- }
- /**
- * Sets up scrolls array, determines if frames are involved.
- */
- function init() {
- if (initDone || !document.body) return;
- initDone = true;
- var body = document.body;
- var html = document.documentElement;
- var windowHeight = window.innerHeight;
- var scrollHeight = body.scrollHeight;
- // check compat mode for root element
- root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
- activeElement = body;
- initTest();
- // Checks if this script is running in a frame
- if (top != self) {
- isFrame = true;
- }
- /**
- * Safari 10 fixed it, Chrome fixed it in v45:
- * This fixes a bug where the areas left and right to
- * the content does not trigger the onmousewheel event
- * on some pages. e.g.: html, body { height: 100% }
- */
- else if (isOldSafari &&
- scrollHeight > windowHeight &&
- (body.offsetHeight <= windowHeight ||
- html.offsetHeight <= windowHeight)) {
- var fullPageElem = document.createElement('div');
- fullPageElem.style.cssText = 'position:absolute; z-index:-10000; ' +
- 'top:0; left:0; right:0; height:' +
- root.scrollHeight + 'px';
- document.body.appendChild(fullPageElem);
- // DOM changed (throttled) to fix height
- var pendingRefresh;
- refreshSize = function () {
- if (pendingRefresh) return; // could also be: clearTimeout(pendingRefresh);
- pendingRefresh = setTimeout(function () {
- if (isExcluded) return; // could be running after cleanup
- fullPageElem.style.height = '0';
- fullPageElem.style.height = root.scrollHeight + 'px';
- pendingRefresh = null;
- }, 500); // act rarely to stay fast
- };
- setTimeout(refreshSize, 10);
- addEvent('resize', refreshSize);
- // TODO: attributeFilter?
- var config = {
- attributes: true,
- childList: true,
- characterData: false
- // subtree: true
- };
- observer = new MutationObserver(refreshSize);
- observer.observe(body, config);
- if (root.offsetHeight <= windowHeight) {
- var clearfix = document.createElement('div');
- clearfix.style.clear = 'both';
- body.appendChild(clearfix);
- }
- }
- // disable fixed background
- if (!options.fixedBackground && !isExcluded) {
- body.style.backgroundAttachment = 'scroll';
- html.style.backgroundAttachment = 'scroll';
- }
- }
- /**
- * Removes event listeners and other traces left on the page.
- */
- function cleanup() {
- observer && observer.disconnect();
- removeEvent(wheelEvent, wheel);
- removeEvent('mousedown', mousedown);
- removeEvent('keydown', keydown);
- removeEvent('resize', refreshSize);
- removeEvent('load', init);
- }
- /************************************************
- * SCROLLING
- ************************************************/
- var que = [];
- var pending = false;
- var lastScroll = Date.now();
- /**
- * Pushes scroll actions to the scrolling queue.
- */
- function scrollArray(elem, left, top) {
- directionCheck(left, top);
- if (options.accelerationMax != 1) {
- var now = Date.now();
- var elapsed = now - lastScroll;
- if (elapsed < options.accelerationDelta) {
- var factor = (1 + (50 / elapsed)) / 2;
- if (factor > 1) {
- factor = Math.min(factor, options.accelerationMax);
- left *= factor;
- top *= factor;
- }
- }
- lastScroll = Date.now();
- }
- // push a scroll command
- que.push({
- x: left,
- y: top,
- lastX: (left < 0) ? 0.99 : -0.99,
- lastY: (top < 0) ? 0.99 : -0.99,
- start: Date.now()
- });
- // don't act if there's a pending queue
- if (pending) {
- return;
- }
- var scrollRoot = getScrollRoot();
- var isWindowScroll = (elem === scrollRoot || elem === document.body);
- // if we haven't already fixed the behavior,
- // and it needs fixing for this sesh
- if (elem.$scrollBehavior == null && isScrollBehaviorSmooth(elem)) {
- elem.$scrollBehavior = elem.style.scrollBehavior;
- elem.style.scrollBehavior = 'auto';
- }
- var step = function (time) {
- var now = Date.now();
- var scrollX = 0;
- var scrollY = 0;
- for (var i = 0; i < que.length; i++) {
- var item = que[i];
- var elapsed = now - item.start;
- var finished = (elapsed >= options.animationTime);
- // scroll position: [0, 1]
- var position = (finished) ? 1 : elapsed / options.animationTime;
- // easing [optional]
- if (options.pulseAlgorithm) {
- position = pulse(position);
- }
- // only need the difference
- var x = (item.x * position - item.lastX) >> 0;
- var y = (item.y * position - item.lastY) >> 0;
- // add this to the total scrolling
- scrollX += x;
- scrollY += y;
- // update last values
- item.lastX += x;
- item.lastY += y;
- // delete and step back if it's over
- if (finished) {
- que.splice(i, 1); i--;
- }
- }
- // scroll left and top
- if (isWindowScroll) {
- window.scrollBy(scrollX, scrollY);
- }
- else {
- if (scrollX) elem.scrollLeft += scrollX;
- if (scrollY) elem.scrollTop += scrollY;
- }
- // clean up if there's nothing left to do
- if (!left && !top) {
- que = [];
- }
- if (que.length) {
- requestFrame(step, elem, (1000 / options.frameRate + 1));
- } else {
- pending = false;
- // restore default behavior at the end of scrolling sesh
- if (elem.$scrollBehavior != null) {
- elem.style.scrollBehavior = elem.$scrollBehavior;
- elem.$scrollBehavior = null;
- }
- }
- };
- // start a new queue of actions
- requestFrame(step, elem, 0);
- pending = true;
- }
- /***********************************************
- * EVENTS
- ***********************************************/
- /**
- * Mouse wheel handler.
- * @param {Object} event
- */
- function wheel(event) {
- if (!initDone) {
- init();
- }
- var target = event.target;
- // leave early if default action is prevented
- // or it's a zooming event with CTRL
- if (event.defaultPrevented || event.ctrlKey) {
- return true;
- }
- // leave embedded content alone (flash & pdf)
- if (isNodeName(activeElement, 'embed') ||
- (isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) ||
- isNodeName(activeElement, 'object') ||
- target.shadowRoot) {
- return true;
- }
- var deltaX = -event.wheelDeltaX || event.deltaX || 0;
- var deltaY = -event.wheelDeltaY || event.deltaY || 0;
- if (isMac) {
- if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) {
- deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX));
- }
- if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) {
- deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY));
- }
- }
- // use wheelDelta if deltaX/Y is not available
- if (!deltaX && !deltaY) {
- deltaY = -event.wheelDelta || 0;
- }
- // line based scrolling (Firefox mostly)
- if (event.deltaMode === 1) {
- deltaX *= 40;
- deltaY *= 40;
- }
- var overflowing = overflowingAncestor(target);
- // nothing to do if there's no element that's scrollable
- if (!overflowing) {
- // except Chrome iframes seem to eat wheel events, which we need to
- // propagate up, if the iframe has nothing overflowing to scroll
- if (isFrame && isChrome) {
- // change target to iframe element itself for the parent frame
- Object.defineProperty(event, "target", {value: window.frameElement});
- return parent.wheel(event);
- }
- return true;
- }
- // check if it's a touchpad scroll that should be ignored
- if (isTouchpad(deltaY)) {
- return true;
- }
- // scale by step size
- // delta is 120 most of the time
- // synaptics seems to send 1 sometimes
- if (Math.abs(deltaX) > 1.2) {
- deltaX *= options.stepSize / 120;
- }
- if (Math.abs(deltaY) > 1.2) {
- deltaY *= options.stepSize / 120;
- }
- scrollArray(overflowing, deltaX, deltaY);
- event.preventDefault();
- scheduleClearCache();
- }
- /**
- * Keydown event handler.
- * @param {Object} event
- */
- function keydown(event) {
- var target = event.target;
- var modifier = event.ctrlKey || event.altKey || event.metaKey ||
- (event.shiftKey && event.keyCode !== key.spacebar);
- // our own tracked active element could've been removed from the DOM
- if (!document.body.contains(activeElement)) {
- activeElement = document.activeElement;
- }
- // do nothing if user is editing text
- // or using a modifier key (except shift)
- // or in a dropdown
- // or inside interactive elements
- var inputNodeNames = /^(textarea|select|embed|object)$/i;
- var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i;
- if ( event.defaultPrevented ||
- inputNodeNames.test(target.nodeName) ||
- isNodeName(target, 'input') && !buttonTypes.test(target.type) ||
- isNodeName(activeElement, 'video') ||
- isInsideYoutubeVideo(event) ||
- target.isContentEditable ||
- modifier ) {
- return true;
- }
- // [spacebar] should trigger button press, leave it alone
- if ((isNodeName(target, 'button') ||
- isNodeName(target, 'input') && buttonTypes.test(target.type)) &&
- event.keyCode === key.spacebar) {
- return true;
- }
- // [arrwow keys] on radio buttons should be left alone
- if (isNodeName(target, 'input') && target.type == 'radio' &&
- arrowKeys[event.keyCode]) {
- return true;
- }
- var shift, x = 0, y = 0;
- var overflowing = overflowingAncestor(activeElement);
- if (!overflowing) {
- // Chrome iframes seem to eat key events, which we need to
- // propagate up, if the iframe has nothing overflowing to scroll
- return (isFrame && isChrome) ? parent.keydown(event) : true;
- }
- var clientHeight = overflowing.clientHeight;
- if (overflowing == document.body) {
- clientHeight = window.innerHeight;
- }
- switch (event.keyCode) {
- case key.up:
- y = -options.arrowScroll;
- break;
- case key.down:
- y = options.arrowScroll;
- break;
- case key.spacebar: // (+ shift)
- shift = event.shiftKey ? 1 : -1;
- y = -shift * clientHeight * 0.9;
- break;
- case key.pageup:
- y = -clientHeight * 0.9;
- break;
- case key.pagedown:
- y = clientHeight * 0.9;
- break;
- case key.home:
- if (overflowing == document.body && document.scrollingElement)
- overflowing = document.scrollingElement;
- y = -overflowing.scrollTop;
- break;
- case key.end:
- var scroll = overflowing.scrollHeight - overflowing.scrollTop;
- var scrollRemaining = scroll - clientHeight;
- y = (scrollRemaining > 0) ? scrollRemaining + 10 : 0;
- break;
- case key.left:
- x = -options.arrowScroll;
- break;
- case key.right:
- x = options.arrowScroll;
- break;
- default:
- return true; // a key we don't care about
- }
- scrollArray(overflowing, x, y);
- event.preventDefault();
- scheduleClearCache();
- }
- /**
- * Mousedown event only for updating activeElement
- */
- function mousedown(event) {
- activeElement = event.target;
- }
- /***********************************************
- * OVERFLOW
- ***********************************************/
- var uniqueID = (function () {
- var i = 0;
- return function (el) {
- return el.uniqueID || (el.uniqueID = i++);
- };
- })();
- var cacheX = {}; // cleared out after a scrolling session
- var cacheY = {}; // cleared out after a scrolling session
- var clearCacheTimer;
- var smoothBehaviorForElement = {};
- //setInterval(function () { cache = {}; }, 10 * 1000);
- function scheduleClearCache() {
- clearTimeout(clearCacheTimer);
- clearCacheTimer = setInterval(function () {
- cacheX = cacheY = smoothBehaviorForElement = {};
- }, 1*1000);
- }
- function setCache(elems, overflowing, x) {
- var cache = x ? cacheX : cacheY;
- for (var i = elems.length; i--;)
- cache[uniqueID(elems[i])] = overflowing;
- return overflowing;
- }
- function getCache(el, x) {
- return (x ? cacheX : cacheY)[uniqueID(el)];
- }
- // (body) (root)
- // | hidden | visible | scroll | auto |
- // hidden | no | no | YES | YES |
- // visible | no | YES | YES | YES |
- // scroll | no | YES | YES | YES |
- // auto | no | YES | YES | YES |
- function overflowingAncestor(el) {
- var elems = [];
- var body = document.body;
- var rootScrollHeight = root.scrollHeight;
- do {
- var cached = getCache(el, false);
- if (cached) {
- return setCache(elems, cached);
- }
- elems.push(el);
- if (rootScrollHeight === el.scrollHeight) {
- var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body);
- var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root);
- if (isFrame && isContentOverflowing(root) ||
- !isFrame && isOverflowCSS) {
- return setCache(elems, getScrollRoot());
- }
- } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) {
- return setCache(elems, el);
- }
- } while ((el = el.parentElement));
- }
- function isContentOverflowing(el) {
- return (el.clientHeight + 10 < el.scrollHeight);
- }
- // typically for <body> and <html>
- function overflowNotHidden(el) {
- var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
- return (overflow !== 'hidden');
- }
- // for all other elements
- function overflowAutoOrScroll(el) {
- var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
- return (overflow === 'scroll' || overflow === 'auto');
- }
- // for all other elements
- function isScrollBehaviorSmooth(el) {
- var id = uniqueID(el);
- if (smoothBehaviorForElement[id] == null) {
- var scrollBehavior = getComputedStyle(el, '')['scroll-behavior'];
- smoothBehaviorForElement[id] = ('smooth' == scrollBehavior);
- }
- return smoothBehaviorForElement[id];
- }
- /***********************************************
- * HELPERS
- ***********************************************/
- function addEvent(type, fn, arg) {
- window.addEventListener(type, fn, arg || false);
- }
- function removeEvent(type, fn, arg) {
- window.removeEventListener(type, fn, arg || false);
- }
- function isNodeName(el, tag) {
- return el && (el.nodeName||'').toLowerCase() === tag.toLowerCase();
- }
- function directionCheck(x, y) {
- x = (x > 0) ? 1 : -1;
- y = (y > 0) ? 1 : -1;
- if (direction.x !== x || direction.y !== y) {
- direction.x = x;
- direction.y = y;
- que = [];
- lastScroll = 0;
- }
- }
- if (window.localStorage && localStorage.SS_deltaBuffer) {
- try { // #46 Safari throws in private browsing for localStorage
- deltaBuffer = localStorage.SS_deltaBuffer.split(',');
- } catch (e) { }
- }
- function isTouchpad(deltaY) {
- if (!deltaY) return;
- if (!deltaBuffer.length) {
- deltaBuffer = [deltaY, deltaY, deltaY];
- }
- deltaY = Math.abs(deltaY);
- deltaBuffer.push(deltaY);
- deltaBuffer.shift();
- clearTimeout(deltaBufferTimer);
- deltaBufferTimer = setTimeout(function () {
- try { // #46 Safari throws in private browsing for localStorage
- localStorage.SS_deltaBuffer = deltaBuffer.join(',');
- } catch (e) { }
- }, 1000);
- var dpiScaledWheelDelta = deltaY > 120 && allDeltasDivisableBy(deltaY); // win64
- var tp = !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100) && !dpiScaledWheelDelta;
- if (deltaY < 50) return true;
- return tp;
- }
- function isDivisible(n, divisor) {
- return (Math.floor(n / divisor) == n / divisor);
- }
- function allDeltasDivisableBy(divisor) {
- return (isDivisible(deltaBuffer[0], divisor) &&
- isDivisible(deltaBuffer[1], divisor) &&
- isDivisible(deltaBuffer[2], divisor));
- }
- function isInsideYoutubeVideo(event) {
- var elem = event.target;
- var isControl = false;
- if (document.URL.indexOf ('www.youtube.com/watch') != -1) {
- do {
- isControl = (elem.classList &&
- elem.classList.contains('html5-video-controls'));
- if (isControl) break;
- } while ((elem = elem.parentNode));
- }
- return isControl;
- }
- var requestFrame = (function () {
- return (window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- function (callback, element, delay) {
- window.setTimeout(callback, delay || (1000/60));
- });
- })();
- var MutationObserver = (window.MutationObserver ||
- window.WebKitMutationObserver ||
- window.MozMutationObserver);
- var getScrollRoot = (function() {
- var SCROLL_ROOT = document.scrollingElement;
- return function() {
- if (!SCROLL_ROOT) {
- var dummy = document.createElement('div');
- dummy.style.cssText = 'height:10000px;width:1px;';
- document.body.appendChild(dummy);
- var bodyScrollTop = document.body.scrollTop;
- var docElScrollTop = document.documentElement.scrollTop;
- window.scrollBy(0, 3);
- if (document.body.scrollTop != bodyScrollTop)
- (SCROLL_ROOT = document.body);
- else
- (SCROLL_ROOT = document.documentElement);
- window.scrollBy(0, -3);
- document.body.removeChild(dummy);
- }
- return SCROLL_ROOT;
- };
- })();
- /***********************************************
- * PULSE (by Michael Herf)
- ***********************************************/
- /**
- * Viscous fluid with a pulse for part and decay for the rest.
- * - Applies a fixed force over an interval (a damped acceleration), and
- * - Lets the exponential bleed away the velocity over a longer interval
- * - Michael Herf, http://stereopsis.com/stopping/
- */
- function pulse_(x) {
- var val, start, expx;
- // test
- x = x * options.pulseScale;
- if (x < 1) { // acceleartion
- val = x - (1 - Math.exp(-x));
- } else { // tail
- // the previous animation ended here:
- start = Math.exp(-1);
- // simple viscous drag
- x -= 1;
- expx = 1 - Math.exp(-x);
- val = start + (expx * (1 - start));
- }
- return val * options.pulseNormalize;
- }
- function pulse(x) {
- if (x >= 1) return 1;
- if (x <= 0) return 0;
- if (options.pulseNormalize == 1) {
- options.pulseNormalize /= pulse_(1);
- }
- return pulse_(x);
- }
- /***********************************************
- * FIRST RUN
- ***********************************************/
- var userAgent = window.navigator.userAgent;
- var isEdge = /Edge/.test(userAgent); // thank you MS
- var isChrome = /chrome/i.test(userAgent) && !isEdge;
- var isSafari = /safari/i.test(userAgent) && !isEdge;
- var isMobile = /mobile/i.test(userAgent);
- var isIEWin7 = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent);
- var isOldSafari = isSafari && (/Version\/8/i.test(userAgent) || /Version\/9/i.test(userAgent));
- var isEnabledForBrowser = (isChrome || isSafari || isIEWin7) && !isMobile;
- var supportsPassive = false;
- try {
- window.addEventListener("test", null, Object.defineProperty({}, 'passive', {
- get: function () {
- supportsPassive = true;
- }
- }));
- } catch(e) {}
- var wheelOpt = supportsPassive ? { passive: false } : false;
- var wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel';
- if (wheelEvent && isEnabledForBrowser) {
- addEvent(wheelEvent, wheel, wheelOpt);
- addEvent('mousedown', mousedown);
- addEvent('load', init);
- }
- /***********************************************
- * PUBLIC INTERFACE
- ***********************************************/
- function SmoothScroll(optionsToSet) {
- for (var key in optionsToSet)
- if (defaultOptions.hasOwnProperty(key))
- options[key] = optionsToSet[key];
- }
- SmoothScroll.destroy = cleanup;
- if (window.SmoothScrollOptions) // async API
- SmoothScroll(window.SmoothScrollOptions);
- if (typeof define === 'function' && define.amd)
- define(function() {
- return SmoothScroll;
- });
- else if ('object' == typeof exports)
- module.exports = SmoothScroll;
- else
- window.SmoothScroll = SmoothScroll;
- })();
|