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