1// 2// SmoothScroll for websites v1.4.10 (Balazs Galambosi) 3// http://www.smoothscroll.net/ 4// 5// Licensed under the terms of the MIT license. 6// 7// You may use it in your theme if you credit me. 8// It is also free to use on any individual website. 9// 10// Exception: 11// The only restriction is to not publish any 12// extension for browsers or native application 13// without getting a written permission first. 14// 15 16(function () { 17 18// Scroll Variables (tweakable) 19var defaultOptions = { 20 21 frameRate : 150, // [Hz] 22 animationTime : 1000, // [ms] 23 stepSize : 80, // [px] 24 25 // Pulse (less tweakable) 26 // ratio of "tail" to "acceleration" 27 pulseAlgorithm : true, 28 pulseScale : 4, 29 pulseNormalize : 1, 30 31 // Acceleration 32 accelerationDelta : 40, // 50 33 accelerationMax : 3, // 3 34 35 // Keyboard Settings 36 keyboardSupport : true, // option 37 arrowScroll : 50, // [px] 38 39 // Other 40 fixedBackground : true, 41 excluded : '' 42}; 43 44var options = defaultOptions; 45 46 47// Other Variables 48var isExcluded = false; 49var isFrame = false; 50var direction = { x: 0, y: 0 }; 51var initDone = false; 52var root = document.documentElement; 53var activeElement; 54var observer; 55var refreshSize; 56var deltaBuffer = []; 57var deltaBufferTimer; 58var isMac = /^Mac/.test(navigator.platform); 59 60var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, 61 pageup: 33, pagedown: 34, end: 35, home: 36 }; 62var arrowKeys = { 37: 1, 38: 1, 39: 1, 40: 1 }; 63 64/*********************************************** 65 * INITIALIZE 66 ***********************************************/ 67 68/** 69 * Tests if smooth scrolling is allowed. Shuts down everything if not. 70 */ 71function initTest() { 72 if (options.keyboardSupport) { 73 addEvent('keydown', keydown); 74 } 75} 76 77/** 78 * Sets up scrolls array, determines if frames are involved. 79 */ 80function init() { 81 82 if (initDone || !document.body) return; 83 84 initDone = true; 85 86 var body = document.body; 87 var html = document.documentElement; 88 var windowHeight = window.innerHeight; 89 var scrollHeight = body.scrollHeight; 90 91 // check compat mode for root element 92 root = (document.compatMode.indexOf('CSS') >= 0) ? html : body; 93 activeElement = body; 94 95 initTest(); 96 97 // Checks if this script is running in a frame 98 if (top != self) { 99 isFrame = true; 100 } 101 102 /** 103 * Safari 10 fixed it, Chrome fixed it in v45: 104 * This fixes a bug where the areas left and right to 105 * the content does not trigger the onmousewheel event 106 * on some pages. e.g.: html, body { height: 100% } 107 */ 108 else if (isOldSafari && 109 scrollHeight > windowHeight && 110 (body.offsetHeight <= windowHeight || 111 html.offsetHeight <= windowHeight)) { 112 113 var fullPageElem = document.createElement('div'); 114 fullPageElem.style.cssText = 'position:absolute; z-index:-10000; ' + 115 'top:0; left:0; right:0; height:' + 116 root.scrollHeight + 'px'; 117 document.body.appendChild(fullPageElem); 118 119 // DOM changed (throttled) to fix height 120 var pendingRefresh; 121 refreshSize = function () { 122 if (pendingRefresh) return; // could also be: clearTimeout(pendingRefresh); 123 pendingRefresh = setTimeout(function () { 124 if (isExcluded) return; // could be running after cleanup 125 fullPageElem.style.height = '0'; 126 fullPageElem.style.height = root.scrollHeight + 'px'; 127 pendingRefresh = null; 128 }, 500); // act rarely to stay fast 129 }; 130 131 setTimeout(refreshSize, 10); 132 133 addEvent('resize', refreshSize); 134 135 // TODO: attributeFilter? 136 var config = { 137 attributes: true, 138 childList: true, 139 characterData: false 140 // subtree: true 141 }; 142 143 observer = new MutationObserver(refreshSize); 144 observer.observe(body, config); 145 146 if (root.offsetHeight <= windowHeight) { 147 var clearfix = document.createElement('div'); 148 clearfix.style.clear = 'both'; 149 body.appendChild(clearfix); 150 } 151 } 152 153 // disable fixed background
154 if (!options.fixedBackground && !isExcluded) { 155 body.style.backgroundAttachment = 'scroll'; 156 html.style.backgroundAttachment = 'scroll'; 157 } 158} 159 160/** 161 * Removes event listeners and other traces left on the page. 162 */ 163function cleanup() { 164 observer && observer.disconnect(); 165 removeEvent(wheelEvent, wheel); 166 removeEvent('mousedown', mousedown); 167 removeEvent('keydown', keydown); 168 removeEvent('resize', refreshSize); 169 removeEvent('load', init); 170} 171 172 173/************************************************ 174 * SCROLLING 175 ************************************************/ 176 177var que = []; 178var pending = false; 179var lastScroll = Date.now(); 180 181/** 182 * Pushes scroll actions to the scrolling queue. 183 */ 184function scrollArray(elem, left, top) { 185 186 directionCheck(left, top); 187 188 if (options.accelerationMax != 1) { 189 var now = Date.now(); 190 var elapsed = now - lastScroll; 191 if (elapsed < options.accelerationDelta) { 192 var factor = (1 + (50 / elapsed)) / 2; 193 if (factor > 1) { 194 factor = Math.min(factor, options.accelerationMax); 195 left *= factor; 196 top *= factor; 197 } 198 } 199 lastScroll = Date.now(); 200 } 201 202 // push a scroll command 203 que.push({ 204 x: left, 205 y: top, 206 lastX: (left < 0) ? 0.99 : -0.99, 207 lastY: (top < 0) ? 0.99 : -0.99, 208 start: Date.now() 209 }); 210 211 // don't act if there's a pending queue 212 if (pending) { 213 return; 214 } 215 216 var scrollRoot = getScrollRoot(); 217 var isWindowScroll = (elem === scrollRoot || elem === document.body); 218 219 // if we haven't already fixed the behavior, 220 // and it needs fixing for this sesh 221 if (elem.$scrollBehavior == null && isScrollBehaviorSmooth(elem)) { 222 elem.$scrollBehavior = elem.style.scrollBehavior; 223 elem.style.scrollBehavior = 'auto'; 224 } 225 226 var step = function (time) { 227 228 var now = Date.now(); 229 var scrollX = 0; 230 var scrollY = 0; 231 232 for (var i = 0; i < que.length; i++) { 233 234 var item = que[i]; 235 var elapsed = now - item.start; 236 var finished = (elapsed >= options.animationTime); 237 238 // scroll position: [0, 1] 239 var position = (finished) ? 1 : elapsed / options.animationTime; 240 241 // easing [optional] 242 if (options.pulseAlgorithm) { 243 position = pulse(position); 244 } 245 246 // only need the difference 247 var x = (item.x * position - item.lastX) >> 0; 248 var y = (item.y * position - item.lastY) >> 0; 249 250 // add this to the total scrolling 251 scrollX += x; 252 scrollY += y; 253 254 // update last values 255 item.lastX += x; 256 item.lastY += y; 257 258 // delete and step back if it's over 259 if (finished) { 260 que.splice(i, 1); i--; 261 } 262 } 263 264 // scroll left and top 265 if (isWindowScroll) { 266 window.scrollBy(scrollX, scrollY); 267 } 268 else { 269 if (scrollX) elem.scrollLeft += scrollX; 270 if (scrollY) elem.scrollTop += scrollY; 271 } 272 273 // clean up if there's nothing left to do 274 if (!left && !top) { 275 que = []; 276 } 277 278 if (que.length) { 279 requestFrame(step, elem, (1000 / options.frameRate + 1)); 280 } else { 281 pending = false; 282 // restore default behavior at the end of scrolling sesh 283 if (elem.$scrollBehavior != null) { 284 elem.style.scrollBehavior = elem.$scrollBehavior; 285 elem.$scrollBehavior = null; 286 } 287 } 288 }; 289 290 // start a new queue of actions 291 requestFrame(step, elem, 0); 292 pending = true; 293} 294 295 296/*********************************************** 297 * EVENTS 298 ***********************************************/ 299 300/** 301 * Mouse wheel handler. 302 * @param {Object} event 303 */ 304function wheel(event) { 305 306 if (!initDone) { 307 init(); 308 } 309 310 var target = event.target; 311 312 // leave early if default action is prevented 313 // or it's a zooming event with CTRL 314 if (event.defaultPrevented || event.ctrlKey) { 315 return true; 316 } 317 318 // leave embedded content alone (flash & pdf) 319 if (isNodeName(activeElement, 'embed') || 320 (isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) || 321 isNodeName(activeElement, 'object') || 322 target.shadowRoot) { 323 return true; 324 } 325 326 var deltaX = -event.wheelDeltaX || event.deltaX || 0; 327 var deltaY = -event.wheelDeltaY || event.deltaY || 0; 328 329 if (isMac) { 330 if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) { 331 deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX)); 332 } 333 if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) { 334 deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY)); 335 } 336 } 337 338 // use wheelDelta if deltaX/Y is not available 339 if (!deltaX && !deltaY) { 340 deltaY = -event.wheelDelta || 0; 341 } 342 343 // line based scrolling (Firefox mostly) 344 if (event.deltaMode === 1) { 345 deltaX *= 40; 346 deltaY *= 40; 347 } 348 349 var overflowing = overflowingAncestor(target); 350 351 // nothing to do if there's no element that's scrollable 352 if (!overflowing) { 353 // except Chrome iframes seem to eat wheel events, which we need to 354 // propagate up, if the iframe has nothing overflowing to scroll 355 if (isFrame && isChrome) { 356 // change target to iframe element itself for the parent frame 357 Object.defineProperty(event, "target", {value: window.frameElement}); 358 return parent.wheel(event); 359 } 360 return true; 361 } 362 363 // check if it's a touchpad scroll that should be ignored 364 if (isTouchpad(deltaY)) { 365 return true; 366 } 367 368 // scale by step size 369 // delta is 120 most of the time
370 // synaptics seems to send 1 sometimes 371 if (Math.abs(deltaX) > 1.2) { 372 deltaX *= options.stepSize / 120; 373 } 374 if (Math.abs(deltaY) > 1.2) { 375 deltaY *= options.stepSize / 120; 376 } 377 378 scrollArray(overflowing, deltaX, deltaY); 379 event.preventDefault(); 380 scheduleClearCache(); 381} 382 383/** 384 * Keydown event handler. 385 * @param {Object} event 386 */ 387function keydown(event) { 388 389 var target = event.target; 390 var modifier = event.ctrlKey || event.altKey || event.metaKey || 391 (event.shiftKey && event.keyCode !== key.spacebar); 392 393 // our own tracked active element could've been removed from the DOM 394 if (!document.body.contains(activeElement)) { 395 activeElement = document.activeElement; 396 } 397 398 // do nothing if user is editing text 399 // or using a modifier key (except shift) 400 // or in a dropdown 401 // or inside interactive elements 402 var inputNodeNames = /^(textarea|select|embed|object)$/i; 403 var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i; 404 if ( event.defaultPrevented || 405 inputNodeNames.test(target.nodeName) || 406 isNodeName(target, 'input') && !buttonTypes.test(target.type) || 407 isNodeName(activeElement, 'video') || 408 isInsideYoutubeVideo(event) || 409 target.isContentEditable || 410 modifier ) { 411 return true; 412 } 413 414 // [spacebar] should trigger button press, leave it alone 415 if ((isNodeName(target, 'button') || 416 isNodeName(target, 'input') && buttonTypes.test(target.type)) && 417 event.keyCode === key.spacebar) { 418 return true; 419 } 420 421 // [arrwow keys] on radio buttons should be left alone 422 if (isNodeName(target, 'input') && target.type == 'radio' && 423 arrowKeys[event.keyCode]) { 424 return true; 425 } 426 427 var shift, x = 0, y = 0; 428 var overflowing = overflowingAncestor(activeElement); 429 430 if (!overflowing) { 431 // Chrome iframes seem to eat key events, which we need to 432 // propagate up, if the iframe has nothing overflowing to scroll 433 return (isFrame && isChrome) ? parent.keydown(event) : true; 434 } 435 436 var clientHeight = overflowing.clientHeight; 437 438 if (overflowing == document.body) { 439 clientHeight = window.innerHeight; 440 } 441 442 switch (event.keyCode) { 443 case key.up: 444 y = -options.arrowScroll; 445 break; 446 case key.down: 447 y = options.arrowScroll; 448 break; 449 case key.spacebar: // (+ shift) 450 shift = event.shiftKey ? 1 : -1; 451 y = -shift * clientHeight * 0.9; 452 break; 453 case key.pageup: 454 y = -clientHeight * 0.9; 455 break; 456 case key.pagedown: 457 y = clientHeight * 0.9; 458 break; 459 case key.home: 460 if (overflowing == document.body && document.scrollingElement) 461 overflowing = document.scrollingElement; 462 y = -overflowing.scrollTop; 463 break; 464 case key.end: 465 var scroll = overflowing.scrollHeight - overflowing.scrollTop; 466 var scrollRemaining = scroll - clientHeight; 467 y = (scrollRemaining > 0) ? scrollRemaining + 10 : 0; 468 break; 469 case key.left: 470 x = -options.arrowScroll; 471 break; 472 case key.right: 473 x = options.arrowScroll; 474 break; 475 default: 476 return true; // a key we don't care about 477 } 478 479 scrollArray(overflowing, x, y); 480 event.preventDefault(); 481 scheduleClearCache(); 482} 483 484/** 485 * Mousedown event only for updating activeElement 486 */ 487function mousedown(event) { 488 activeElement = event.target; 489} 490 491 492/*********************************************** 493 * OVERFLOW 494 ***********************************************/ 495 496var uniqueID = (function () { 497 var i = 0; 498 return function (el) { 499 return el.uniqueID || (el.uniqueID = i++); 500 }; 501})(); 502 503var cacheX = {}; // cleared out after a scrolling session 504var cacheY = {}; // cleared out after a scrolling session 505var clearCacheTimer; 506var smoothBehaviorForElement = {}; 507 508//setInterval(function () { cache = {}; }, 10 * 1000); 509 510function scheduleClearCache() { 511 clearTimeout(clearCacheTimer); 512 clearCacheTimer = setInterval(function () { 513 cacheX = cacheY = smoothBehaviorForElement = {}; 514 }, 1*1000); 515} 516
517function setCache(elems, overflowing, x) { 518 var cache = x ? cacheX : cacheY; 519 for (var i = elems.length; i--;) 520 cache[uniqueID(elems[i])] = overflowing; 521 return overflowing; 522} 523 524function getCache(el, x) { 525 return (x ? cacheX : cacheY)[uniqueID(el)]; 526} 527 528// (body) (root) 529// | hidden | visible | scroll | auto | 530// hidden | no | no | YES | YES | 531// visible | no | YES | YES | YES | 532// scroll | no | YES | YES | YES | 533// auto | no | YES | YES | YES | 534 535function overflowingAncestor(el) { 536 var elems = []; 537 var body = document.body; 538 var rootScrollHeight = root.scrollHeight; 539 do { 540 var cached = getCache(el, false); 541 if (cached) { 542 return setCache(elems, cached); 543 } 544 elems.push(el); 545 if (rootScrollHeight === el.scrollHeight) { 546 var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body); 547 var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root); 548 if (isFrame && isContentOverflowing(root) || 549 !isFrame && isOverflowCSS) { 550 return setCache(elems, getScrollRoot()); 551 } 552 } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) { 553 return setCache(elems, el); 554 } 555 } while ((el = el.parentElement)); 556} 557 558function isContentOverflowing(el) { 559 return (el.clientHeight + 10 < el.scrollHeight); 560} 561 562// typically for <body> and <html> 563function overflowNotHidden(el) { 564 var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); 565 return (overflow !== 'hidden'); 566} 567 568// for all other elements 569function overflowAutoOrScroll(el) { 570 var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); 571 return (overflow === 'scroll' || overflow === 'auto'); 572} 573 574// for all other elements 575function isScrollBehaviorSmooth(el) { 576 var id = uniqueID(el); 577 if (smoothBehaviorForElement[id] == null) { 578 var scrollBehavior = getComputedStyle(el, '')['scroll-behavior']; 579 smoothBehaviorForElement[id] = ('smooth' == scrollBehavior); 580 } 581 return smoothBehaviorForElement[id]; 582} 583 584 585/*********************************************** 586 * HELPERS 587 ***********************************************/ 588 589function addEvent(type, fn, arg) { 590 window.addEventListener(type, fn, arg || false); 591} 592 593function removeEvent(type, fn, arg) { 594 window.removeEventListener(type, fn, arg || false); 595} 596 597function isNodeName(el, tag) {
598 return el && (el.nodeName||'').toLowerCase() === tag.toLowerCase(); 599} 600 601function directionCheck(x, y) { 602 x = (x > 0) ? 1 : -1; 603 y = (y > 0) ? 1 : -1; 604 if (direction.x !== x || direction.y !== y) { 605 direction.x = x; 606 direction.y = y; 607 que = []; 608 lastScroll = 0; 609 } 610} 611 612if (window.localStorage && localStorage.SS_deltaBuffer) { 613 try { // #46 Safari throws in private browsing for localStorage 614 deltaBuffer = localStorage.SS_deltaBuffer.split(','); 615 } catch (e) { } 616} 617 618function isTouchpad(deltaY) { 619 if (!deltaY) return; 620 if (!deltaBuffer.length) { 621 deltaBuffer = [deltaY, deltaY, deltaY]; 622 } 623 deltaY = Math.abs(deltaY); 624 deltaBuffer.push(deltaY); 625 deltaBuffer.shift(); 626 clearTimeout(deltaBufferTimer); 627 deltaBufferTimer = setTimeout(function () { 628 try { // #46 Safari throws in private browsing for localStorage 629 localStorage.SS_deltaBuffer = deltaBuffer.join(','); 630 } catch (e) { } 631 }, 1000); 632 var dpiScaledWheelDelta = deltaY > 120 && allDeltasDivisableBy(deltaY); // win64 633 var tp = !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100) && !dpiScaledWheelDelta; 634 if (deltaY < 50) return true; 635 return tp; 636} 637 638function isDivisible(n, divisor) { 639 return (Math.floor(n / divisor) == n / divisor); 640} 641 642function allDeltasDivisableBy(divisor) { 643 return (isDivisible(deltaBuffer[0], divisor) && 644 isDivisible(deltaBuffer[1], divisor) && 645 isDivisible(deltaBuffer[2], divisor)); 646} 647 648function isInsideYoutubeVideo(event) { 649 var elem = event.target; 650 var isControl = false; 651 if (document.URL.indexOf ('www.youtube.com/watch') != -1) { 652 do { 653 isControl = (elem.classList && 654 elem.classList.contains('html5-video-controls')); 655 if (isControl) break; 656 } while ((elem = elem.parentNode)); 657 } 658 return isControl; 659} 660 661var requestFrame = (function () { 662 return (window.requestAnimationFrame || 663 window.webkitRequestAnimationFrame || 664 window.mozRequestAnimationFrame || 665 function (callback, element, delay) { 666 window.setTimeout(callback, delay || (1000/60)); 667 }); 668})(); 669 670var MutationObserver = (window.MutationObserver || 671 window.WebKitMutationObserver || 672 window.MozMutationObserver); 673 674var getScrollRoot = (function() { 675 var SCROLL_ROOT = document.scrollingElement; 676 return function() { 677 if (!SCROLL_ROOT) { 678 var dummy = document.createElement('div'); 679 dummy.style.cssText = 'height:10000px;width:1px;'; 680 document.body.appendChild(dummy); 681 var bodyScrollTop = document.body.scrollTop; 682 var docElScrollTop = document.documentElement.scrollTop; 683 window.scrollBy(0, 3); 684 if (document.body.scrollTop != bodyScrollTop) 685 (SCROLL_ROOT = document.body); 686 else 687 (SCROLL_ROOT = document.documentElement); 688 window.scrollBy(0, -3); 689 document.body.removeChild(dummy); 690 } 691 return SCROLL_ROOT; 692 }; 693})(); 694 695 696/*********************************************** 697 * PULSE (by Michael Herf) 698 ***********************************************/ 699 700/** 701 * Viscous fluid with a pulse for part and decay for the rest. 702 * - Applies a fixed force over an interval (a damped acceleration), and 703 * - Lets the exponential bleed away the velocity over a longer interval 704 * - Michael Herf, http://stereopsis.com/stopping/ 705 */ 706function pulse_(x) { 707 var val, start, expx; 708 // test 709 x = x * options.pulseScale; 710 if (x < 1) { // acceleartion 711 val = x - (1 - Math.exp(-x)); 712 } else { // tail 713 // the previous animation ended here: 714 start = Math.exp(-1); 715 // simple viscous drag 716 x -= 1; 717 expx = 1 - Math.exp(-x); 718 val = start + (expx * (1 - start)); 719 } 720 return val * options.pulseNormalize; 721} 722 723function pulse(x) { 724 if (x >= 1) return 1; 725 if (x <= 0) return 0; 726 727 if (options.pulseNormalize == 1) { 728 options.pulseNormalize /= pulse_(1); 729 } 730 return pulse_(x); 731} 732 733 734/*********************************************** 735 * FIRST RUN 736 ***********************************************/ 737 738var userAgent = window.navigator.userAgent; 739var isEdge = /Edge/.test(userAgent); // thank you MS 740var isChrome = /chrome/i.test(userAgent) && !isEdge; 741var isSafari = /safari/i.test(userAgent) && !isEdge; 742var isMobile = /mobile/i.test(userAgent); 743var isIEWin7 = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent); 744var isOldSafari = isSafari && (/Version\/8/i.test(userAgent) || /Version\/9/i.test(userAgent)); 745var isEnabledForBrowser = (isChrome || isSafari || isIEWin7) && !isMobile; 746 747var supportsPassive = false;
748try { 749 window.addEventListener("test", null, Object.defineProperty({}, 'passive', { 750 get: function () { 751 supportsPassive = true; 752 } 753 })); 754} catch(e) {} 755 756var wheelOpt = supportsPassive ? { passive: false } : false; 757var wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel'; 758 759if (wheelEvent && isEnabledForBrowser) { 760 addEvent(wheelEvent, wheel, wheelOpt); 761 addEvent('mousedown', mousedown); 762 addEvent('load', init); 763} 764 765 766/*********************************************** 767 * PUBLIC INTERFACE 768 ***********************************************/ 769 770function SmoothScroll(optionsToSet) { 771 for (var key in optionsToSet) 772 if (defaultOptions.hasOwnProperty(key)) 773 options[key] = optionsToSet[key]; 774} 775SmoothScroll.destroy = cleanup; 776 777if (window.SmoothScrollOptions) // async API 778 SmoothScroll(window.SmoothScrollOptions); 779 780if (typeof define === 'function' && define.amd) 781 define(function() { 782 return SmoothScroll; 783 }); 784else if ('object' == typeof exports) 785 module.exports = SmoothScroll; 786else 787 window.SmoothScroll = SmoothScroll; 788 789})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.