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