diff options
author | Andy Feng <ayfeng@uci.edu> | 2020-07-16 21:14:06 +0300 |
---|---|---|
committer | Andy Feng <ayfeng@uci.edu> | 2020-07-16 21:14:06 +0300 |
commit | bf04ca3ebb4e576874eb1e8daa13e4cdb8529ccd (patch) | |
tree | 618f8d37254b523c518e64ec4b6826c3437caa6d | |
parent | 8120d9227d95d0c0fc78b0a2828c99c01a61c481 (diff) |
Update smooth-scroll plugin to v1.4.10
Updated the smooth-scroll plugin to the latest release (v1.4.10).
This was done in order to fix jittery scrolling in Chrome 56+.
-rw-r--r-- | static/plugins/smooth-scroll/smooth-scroll.js | 1308 |
1 files changed, 674 insertions, 634 deletions
diff --git a/static/plugins/smooth-scroll/smooth-scroll.js b/static/plugins/smooth-scroll/smooth-scroll.js index cfeb22a..3aeafa9 100644 --- a/static/plugins/smooth-scroll/smooth-scroll.js +++ b/static/plugins/smooth-scroll/smooth-scroll.js @@ -1,750 +1,790 @@ // -// SmoothScroll for websites v1.4.6 (Balazs Galambosi) +// 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. +// 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 +// 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: 60, // [Hz] - animationTime: 1000, // [ms] - stepSize: 80, // [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] +// 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); + } +} - // Other - fixedBackground: true, - excluded: '' - }; +/** + * Sets up scrolls array, determines if frames are involved. + */ +function init() { - var options = defaultOptions; + if (initDone || !document.body) return; + initDone = true; - // 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 isMac = /^Mac/.test(navigator.platform); + var body = document.body; + var html = document.documentElement; + var windowHeight = window.innerHeight; + var scrollHeight = body.scrollHeight; - 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 }; + // check compat mode for root element + root = (document.compatMode.indexOf('CSS') >= 0) ? html : body; + activeElement = body; - /*********************************************** - * INITIALIZE - ***********************************************/ + initTest(); - /** - * Tests if smooth scrolling is allowed. Shuts down everything if not. - */ - function initTest() { - if (options.keyboardSupport) { - addEvent('keydown', keydown); - } + // Checks if this script is running in a frame + if (top != self) { + isFrame = true; } /** - * Sets up scrolls array, determines if frames are involved. + * 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% } */ - 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; + 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 + }; - // check compat mode for root element - root = (document.compatMode.indexOf('CSS') >= 0) ? html : body; - activeElement = body; + setTimeout(refreshSize, 10); - initTest(); + addEvent('resize', refreshSize); - // Checks if this script is running in a frame - if (top != self) { - isFrame = true; - } + // TODO: attributeFilter? + var config = { + attributes: true, + childList: true, + characterData: false + // subtree: 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); - } - } + observer = new MutationObserver(refreshSize); + observer.observe(body, config); - // disable fixed background - if (!options.fixedBackground && !isExcluded) { - body.style.backgroundAttachment = 'scroll'; - html.style.backgroundAttachment = 'scroll'; + if (root.offsetHeight <= windowHeight) { + var clearfix = document.createElement('div'); + clearfix.style.clear = 'both'; + body.appendChild(clearfix); } } - /** - * 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); + // disable fixed background + if (!options.fixedBackground && !isExcluded) { + body.style.backgroundAttachment = 'scroll'; + html.style.backgroundAttachment = 'scroll'; } - - - /************************************************ - * 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; - } +} + +/** + * 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(); } + 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 scrollWindow = (elem === document.body); - - 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; + // 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; + } - // easing [optional] - if (options.pulseAlgorithm) { - position = pulse(position); - } + var scrollRoot = getScrollRoot(); + var isWindowScroll = (elem === scrollRoot || elem === document.body); - // only need the difference - var x = (item.x * position - item.lastX) >> 0; - var y = (item.y * position - item.lastY) >> 0; + // 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'; + } - // add this to the total scrolling - scrollX += x; - scrollY += y; + var step = function (time) { - // update last values - item.lastX += x; - item.lastY += y; + var now = Date.now(); + var scrollX = 0; + var scrollY = 0; - // delete and step back if it's over - if (finished) { - que.splice(i, 1); i--; - } - } + for (var i = 0; i < que.length; i++) { - // scroll left and top - if (scrollWindow) { - window.scrollBy(scrollX, scrollY); - } - else { - if (scrollX) elem.scrollLeft += scrollX; - if (scrollY) elem.scrollTop += scrollY; - } + var item = que[i]; + var elapsed = now - item.start; + var finished = (elapsed >= options.animationTime); - // clean up if there's nothing left to do - if (!left && !top) { - que = []; - } + // scroll position: [0, 1] + var position = (finished) ? 1 : elapsed / options.animationTime; - if (que.length) { - requestFrame(step, elem, (1000 / options.frameRate + 1)); - } else { - pending = false; + // easing [optional] + if (options.pulseAlgorithm) { + position = pulse(position); } - }; - - // start a new queue of actions - requestFrame(step, elem, 0); - pending = true; - } + // only need the difference + var x = (item.x * position - item.lastX) >> 0; + var y = (item.y * position - item.lastY) >> 0; - /*********************************************** - * EVENTS - ***********************************************/ + // add this to the total scrolling + scrollX += x; + scrollY += y; - /** - * Mouse wheel handler. - * @param {Object} event - */ - function wheel(event) { + // update last values + item.lastX += x; + item.lastY += y; - 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; + // delete and step back if it's over + if (finished) { + que.splice(i, 1); i--; + } } - // 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; + // scroll left and top + if (isWindowScroll) { + window.scrollBy(scrollX, scrollY); } - - 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)); - } + else { + if (scrollX) elem.scrollLeft += scrollX; + if (scrollY) elem.scrollTop += scrollY; } - // use wheelDelta if deltaX/Y is not available - if (!deltaX && !deltaY) { - deltaY = -event.wheelDelta || 0; + // clean up if there's nothing left to do + if (!left && !top) { + que = []; } - // line based scrolling (Firefox mostly) - if (event.deltaMode === 1) { - deltaX *= 40; - deltaY *= 40; + 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; + } } + }; - var overflowing = overflowingAncestor(target); + // start a new queue of actions + requestFrame(step, elem, 0); + pending = true; +} - // 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; - } +/*********************************************** + * EVENTS + ***********************************************/ - // 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; - } +/** + * Mouse wheel handler. + * @param {Object} event + */ +function wheel(event) { - scrollArray(overflowing, deltaX, deltaY); - event.preventDefault(); - scheduleClearCache(); + if (!initDone) { + init(); } - /** - * Keydown event handler. - * @param {Object} event - */ - function keydown(event) { + var target = event.target; - var target = event.target; - var modifier = event.ctrlKey || event.altKey || event.metaKey || - (event.shiftKey && event.keyCode !== key.spacebar); + // leave early if default action is prevented + // or it's a zooming event with CTRL + if (event.defaultPrevented || event.ctrlKey) { + return true; + } - // our own tracked active element could've been removed from the DOM - if (!document.body.contains(activeElement)) { - activeElement = document.activeElement; - } + // 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; + } - // 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; - } + var deltaX = -event.wheelDeltaX || event.deltaX || 0; + var deltaY = -event.wheelDeltaY || event.deltaY || 0; - // [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; + if (isMac) { + if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) { + deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX)); } - - // [arrwow keys] on radio buttons should be left alone - if (isNodeName(target, 'input') && target.type == 'radio' && - arrowKeys[event.keyCode]) { - return true; + if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) { + deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY)); } + } - 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; - } + // use wheelDelta if deltaX/Y is not available + if (!deltaX && !deltaY) { + deltaY = -event.wheelDelta || 0; + } - var clientHeight = overflowing.clientHeight; + // line based scrolling (Firefox mostly) + if (event.deltaMode === 1) { + deltaX *= 40; + deltaY *= 40; + } - if (overflowing == document.body) { - clientHeight = window.innerHeight; - } + var overflowing = overflowingAncestor(target); - 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: - 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 + // 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); } - - scrollArray(overflowing, x, y); - event.preventDefault(); - scheduleClearCache(); + return true; } - /** - * Mousedown event only for updating activeElement - */ - function mousedown(event) { - activeElement = event.target; + // 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; + } - /*********************************************** - * OVERFLOW - ***********************************************/ - - var uniqueID = (function () { - var i = 0; - return function (el) { - return el.uniqueID || (el.uniqueID = i++); - }; - })(); + scrollArray(overflowing, deltaX, deltaY); + event.preventDefault(); + scheduleClearCache(); +} - var cache = {}; // cleared out after a scrolling session - var clearCacheTimer; +/** + * Keydown event handler. + * @param {Object} event + */ +function keydown(event) { - //setInterval(function () { cache = {}; }, 10 * 1000); + var target = event.target; + var modifier = event.ctrlKey || event.altKey || event.metaKey || + (event.shiftKey && event.keyCode !== key.spacebar); - function scheduleClearCache() { - clearTimeout(clearCacheTimer); - clearCacheTimer = setInterval(function () { cache = {}; }, 1 * 1000); + // our own tracked active element could've been removed from the DOM + if (!document.body.contains(activeElement)) { + activeElement = document.activeElement; } - function setCache(elems, overflowing) { - for (var i = elems.length; i--;) - cache[uniqueID(elems[i])] = overflowing; - return overflowing; + // 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; } - // (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 = cache[uniqueID(el)]; - 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); + // [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; } - function isContentOverflowing(el) { - return (el.clientHeight + 10 < el.scrollHeight); + // [arrwow keys] on radio buttons should be left alone + if (isNodeName(target, 'input') && target.type == 'radio' && + arrowKeys[event.keyCode]) { + return true; } - // typically for <body> and <html> - function overflowNotHidden(el) { - var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); - return (overflow !== 'hidden'); - } + var shift, x = 0, y = 0; + var overflowing = overflowingAncestor(activeElement); - // for all other elements - function overflowAutoOrScroll(el) { - var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); - return (overflow === 'scroll' || overflow === 'auto'); + 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; - /*********************************************** - * HELPERS - ***********************************************/ - - function addEvent(type, fn) { - window.addEventListener(type, fn, false); + if (overflowing == document.body) { + clientHeight = window.innerHeight; } - function removeEvent(type, fn) { - window.removeEventListener(type, fn, false); + 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 } - function isNodeName(el, tag) { - return (el.nodeName || '').toLowerCase() === tag.toLowerCase(); - } + scrollArray(overflowing, x, y); + event.preventDefault(); + scheduleClearCache(); +} - 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; - } - } +/** + * Mousedown event only for updating activeElement + */ +function mousedown(event) { + activeElement = event.target; +} - var deltaBufferTimer; - if (window.localStorage && localStorage.SS_deltaBuffer) { - try { // #46 Safari throws in private browsing for localStorage - deltaBuffer = localStorage.SS_deltaBuffer.split(','); - } catch (e) { } - } +/*********************************************** + * OVERFLOW + ***********************************************/ - function isTouchpad(deltaY) { - if (!deltaY) return; - if (!deltaBuffer.length) { - deltaBuffer = [deltaY, deltaY, deltaY]; +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); } - 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); - return !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100); + } 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); } - - function isDivisible(n, divisor) { - return (Math.floor(n / divisor) == n / divisor); + 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; } - - function allDeltasDivisableBy(divisor) { - return (isDivisible(deltaBuffer[0], divisor) && +} + +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; - 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 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)); } - - function pulse(x) { - if (x >= 1) return 1; - if (x <= 0) return 0; - - if (options.pulseNormalize == 1) { - options.pulseNormalize /= pulse_(1); - } - return pulse_(x); + 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); } - - - /*********************************************** - * 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 wheelEvent; - if ('onwheel' in document.createElement('div')) - wheelEvent = 'wheel'; - else if ('onmousewheel' in document.createElement('div')) - wheelEvent = 'mousewheel'; - - if (wheelEvent && isEnabledForBrowser) { - addEvent(wheelEvent, wheel); - addEvent('mousedown', mousedown); - addEvent('load', init); + 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; - /*********************************************** - * PUBLIC INTERFACE - ***********************************************/ - - function SmoothScroll(optionsToSet) { - for (var key in optionsToSet) - if (defaultOptions.hasOwnProperty(key)) - options[key] = optionsToSet[key]; + if (options.pulseNormalize == 1) { + options.pulseNormalize /= pulse_(1); } - 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; - -})();
\ No newline at end of file + 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; + +})(); |