1// 2// SmoothScroll for websites v1.4.6 (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 // Scrolling Core 22 frameRate : 150, // [Hz] 23 animationTime : 400, // [ms] 24 stepSize : 100, // [px] 25 26 // Pulse (less tweakable) 27 // ratio of "tail" to "acceleration" 28 pulseAlgorithm : true, 29 pulseScale : 4, 30 pulseNormalize : 1, 31 32 // Acceleration 33 accelerationDelta : 50, // 50 34 accelerationMax : 3, // 3 35 36 // Keyboard Settings 37 keyboardSupport : true, // option 38 arrowScroll : 50, // [px] 39 40 // Other 41 fixedBackground : true, 42 excluded : '' 43}; 44 45var options = defaultOptions; 46 47 48// Other Variables 49var isExcluded = false; 50var isFrame = false; 51var direction = { x: 0, y: 0 }; 52var initDone = false; 53var root = document.documentElement; 54var activeElement; 55var observer; 56var refreshSize; 57var deltaBuffer = []; 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
vendor: 5,594 bytes, lines 154-356
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 scrollWindow = (elem === document.body); 217 218 var step = function (time) { 219 220 var now = Date.now(); 221 var scrollX = 0; 222 var scrollY = 0; 223 224 for (var i = 0; i < que.length; i++) { 225 226 var item = que[i]; 227 var elapsed = now - item.start; 228 var finished = (elapsed >= options.animationTime); 229 230 // scroll position: [0, 1] 231 var position = (finished) ? 1 : elapsed / options.animationTime; 232 233 // easing [optional] 234 if (options.pulseAlgorithm) { 235 position = pulse(position); 236 } 237 238 // only need the difference 239 var x = (item.x * position - item.lastX) >> 0; 240 var y = (item.y * position - item.lastY) >> 0; 241 242 // add this to the total scrolling 243 scrollX += x; 244 scrollY += y; 245 246 // update last values 247 item.lastX += x; 248 item.lastY += y; 249 250 // delete and step back if it's over 251 if (finished) { 252 que.splice(i, 1); i--; 253 } 254 } 255 256 // scroll left and top 257 if (scrollWindow) { 258 window.scrollBy(scrollX, scrollY); 259 } 260 else { 261 if (scrollX) elem.scrollLeft += scrollX; 262 if (scrollY) elem.scrollTop += scrollY; 263 } 264 265 // clean up if there's nothing left to do 266 if (!left && !top) { 267 que = []; 268 } 269 270 if (que.length) { 271 requestFrame(step, elem, (1000 / options.frameRate + 1)); 272 } else { 273 pending = false; 274 } 275 }; 276 277 // start a new queue of actions 278 requestFrame(step, elem, 0); 279 pending = true; 280} 281 282 283/*********************************************** 284 * EVENTS 285 ***********************************************/ 286 287/** 288 * Mouse wheel handler. 289 * @param {Object} event 290 */ 291function wheel(event) { 292 293 if (!initDone) { 294 init(); 295 } 296 297 var target = event.target; 298 299 // leave early if default action is prevented 300 // or it's a zooming event with CTRL 301 if (event.defaultPrevented || event.ctrlKey) { 302 return true; 303 } 304 305 // leave embedded content alone (flash & pdf) 306 if (isNodeName(activeElement, 'embed') || 307 (isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) || 308 isNodeName(activeElement, 'object') || 309 target.shadowRoot) { 310 return true; 311 } 312 313 var deltaX = -event.wheelDeltaX || event.deltaX || 0; 314 var deltaY = -event.wheelDeltaY || event.deltaY || 0; 315 316 if (isMac) { 317 if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) { 318 deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX)); 319 } 320 if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) { 321 deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY)); 322 } 323 } 324 325 // use wheelDelta if deltaX/Y is not available 326 if (!deltaX && !deltaY) { 327 deltaY = -event.wheelDelta || 0; 328 } 329 330 // line based scrolling (Firefox mostly) 331 if (event.deltaMode === 1) { 332 deltaX *= 40; 333 deltaY *= 40; 334 } 335 336 var overflowing = overflowingAncestor(target); 337 338 // nothing to do if there's no element that's scrollable 339 if (!overflowing) { 340 // except Chrome iframes seem to eat wheel events, which we need to 341 // propagate up, if the iframe has nothing overflowing to scroll 342 if (isFrame && isChrome) { 343 // change target to iframe element itself for the parent frame 344 Object.defineProperty(event, "target", {value: window.frameElement}); 345 return parent.wheel(event); 346 } 347 return true; 348 } 349 350 // check if it's a touchpad scroll that should be ignored 351 if (isTouchpad(deltaY)) { 352 return true; 353 } 354 355 // scale by step size 356 // delta is 120 most of the time
357 // synaptics seems to send 1 sometimes 358 if (Math.abs(deltaX) > 1.2) { 359 deltaX *= options.stepSize / 120; 360 } 361 if (Math.abs(deltaY) > 1.2) { 362 deltaY *= options.stepSize / 120; 363 } 364 365 scrollArray(overflowing, deltaX, deltaY); 366 //event.preventDefault(); 367 scheduleClearCache(); 368} 369 370/** 371 * Keydown event handler. 372 * @param {Object} event 373 */ 374function keydown(event) { 375 376 var target = event.target; 377 var modifier = event.ctrlKey || event.altKey || event.metaKey || 378 (event.shiftKey && event.keyCode !== key.spacebar); 379 380 // our own tracked active element could've been removed from the DOM 381 if (!document.body.contains(activeElement)) { 382 activeElement = document.activeElement; 383 } 384 385 // do nothing if user is editing text 386 // or using a modifier key (except shift) 387 // or in a dropdown 388 // or inside interactive elements 389 var inputNodeNames = /^(textarea|select|embed|object)$/i; 390 var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i; 391 if ( event.defaultPrevented || 392 inputNodeNames.test(target.nodeName) || 393 isNodeName(target, 'input') && !buttonTypes.test(target.type) || 394 isNodeName(activeElement, 'video') || 395 isInsideYoutubeVideo(event) || 396 target.isContentEditable || 397 modifier ) { 398 return true; 399 } 400 401 // [spacebar] should trigger button press, leave it alone 402 if ((isNodeName(target, 'button') || 403 isNodeName(target, 'input') && buttonTypes.test(target.type)) && 404 event.keyCode === key.spacebar) { 405 return true; 406 } 407 408 // [arrwow keys] on radio buttons should be left alone 409 if (isNodeName(target, 'input') && target.type == 'radio' && 410 arrowKeys[event.keyCode]) { 411 return true; 412 } 413 414 var shift, x = 0, y = 0; 415 var overflowing = overflowingAncestor(activeElement); 416 417 if (!overflowing) { 418 // Chrome iframes seem to eat key events, which we need to 419 // propagate up, if the iframe has nothing overflowing to scroll 420 return (isFrame && isChrome) ? parent.keydown(event) : true; 421 } 422 423 var clientHeight = overflowing.clientHeight; 424 425 if (overflowing == document.body) { 426 clientHeight = window.innerHeight; 427 } 428 429 switch (event.keyCode) { 430 case key.up: 431 y = -options.arrowScroll; 432 break; 433 case key.down: 434 y = options.arrowScroll; 435 break; 436 case key.spacebar: // (+ shift) 437 shift = event.shiftKey ? 1 : -1; 438 y = -shift * clientHeight * 0.9; 439 break; 440 case key.pageup: 441 y = -clientHeight * 0.9; 442 break; 443 case key.pagedown: 444 y = clientHeight * 0.9; 445 break; 446 case key.home: 447 y = -overflowing.scrollTop; 448 break; 449 case key.end: 450 var scroll = overflowing.scrollHeight - overflowing.scrollTop; 451 var scrollRemaining = scroll - clientHeight; 452 y = (scrollRemaining > 0) ? scrollRemaining + 10 : 0; 453 break; 454 case key.left: 455 x = -options.arrowScroll; 456 break; 457 case key.right: 458 x = options.arrowScroll; 459 break; 460 default: 461 return true; // a key we don't care about 462 } 463 464 scrollArray(overflowing, x, y); 465 //event.preventDefault(); 466 scheduleClearCache(); 467} 468 469/** 470 * Mousedown event only for updating activeElement 471 */ 472function mousedown(event) { 473 activeElement = event.target; 474} 475 476 477/*********************************************** 478 * OVERFLOW 479 ***********************************************/ 480 481var uniqueID = (function () { 482 var i = 0; 483 return function (el) { 484 return el.uniqueID || (el.uniqueID = i++); 485 }; 486})(); 487 488var cache = {}; // cleared out after a scrolling session 489var clearCacheTimer; 490 491//setInterval(function () { cache = {}; }, 10 * 1000); 492 493function scheduleClearCache() { 494 clearTimeout(clearCacheTimer); 495 clearCacheTimer = setInterval(function () { cache = {}; }, 1*1000); 496} 497
498function setCache(elems, overflowing) { 499 for (var i = elems.length; i--;) 500 cache[uniqueID(elems[i])] = overflowing; 501 return overflowing; 502} 503 504// (body) (root) 505// | hidden | visible | scroll | auto | 506// hidden | no | no | YES | YES | 507// visible | no | YES | YES | YES | 508// scroll | no | YES | YES | YES | 509// auto | no | YES | YES | YES | 510 511function overflowingAncestor(el) { 512 var elems = []; 513 var body = document.body; 514 var rootScrollHeight = root.scrollHeight; 515 do { 516 var cached = cache[uniqueID(el)]; 517 if (cached) { 518 return setCache(elems, cached); 519 } 520 elems.push(el); 521 if (rootScrollHeight === el.scrollHeight) { 522 var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body); 523 var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root); 524 if (isFrame && isContentOverflowing(root) || 525 !isFrame && isOverflowCSS) { 526 return setCache(elems, getScrollRoot()); 527 } 528 } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) { 529 return setCache(elems, el); 530 } 531 } while (el = el.parentElement); 532} 533 534function isContentOverflowing(el) { 535 return (el.clientHeight + 10 < el.scrollHeight); 536} 537 538// typically for <body> and <html> 539function overflowNotHidden(el) { 540 var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); 541 return (overflow !== 'hidden'); 542} 543 544// for all other elements 545function overflowAutoOrScroll(el) { 546 var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); 547 return (overflow === 'scroll' || overflow === 'auto'); 548} 549 550 551/*********************************************** 552 * HELPERS 553 ***********************************************/ 554 555function addEvent(type, fn) { 556 window.addEventListener(type, fn, false); 557} 558 559function removeEvent(type, fn) { 560 window.removeEventListener(type, fn, false); 561} 562 563function isNodeName(el, tag) { 564 return (el.nodeName||'').toLowerCase() === tag.toLowerCase(); 565} 566 567function directionCheck(x, y) { 568 x = (x > 0) ? 1 : -1; 569 y = (y > 0) ? 1 : -1; 570 if (direction.x !== x || direction.y !== y) { 571 direction.x = x; 572 direction.y = y; 573 que = []; 574 lastScroll = 0; 575 } 576} 577 578var deltaBufferTimer; 579 580if (window.localStorage && localStorage.SS_deltaBuffer) { 581 try { // #46 Safari throws in private browsing for localStorage 582 deltaBuffer = localStorage.SS_deltaBuffer.split(','); 583 } catch (e) { } 584} 585 586function isTouchpad(deltaY) { 587 if (!deltaY) return; 588 if (!deltaBuffer.length) { 589 deltaBuffer = [deltaY, deltaY, deltaY]; 590 } 591 deltaY = Math.abs(deltaY); 592 deltaBuffer.push(deltaY); 593 deltaBuffer.shift(); 594 clearTimeout(deltaBufferTimer); 595 deltaBufferTimer = setTimeout(function () { 596 try { // #46 Safari throws in private browsing for localStorage 597 localStorage.SS_deltaBuffer = deltaBuffer.join(','); 598 } catch (e) { } 599 }, 1000); 600 return !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100); 601} 602 603function isDivisible(n, divisor) { 604 return (Math.floor(n / divisor) == n / divisor); 605} 606 607function allDeltasDivisableBy(divisor) { 608 return (isDivisible(deltaBuffer[0], divisor) && 609 isDivisible(deltaBuffer[1], divisor) && 610 isDivisible(deltaBuffer[2], divisor)); 611} 612 613function isInsideYoutubeVideo(event) { 614 var elem = event.target; 615 var isControl = false; 616 if (document.URL.indexOf ('www.youtube.com/watch') != -1) { 617 do { 618 isControl = (elem.classList && 619 elem.classList.contains('html5-video-controls')); 620 if (isControl) break; 621 } while (elem = elem.parentNode); 622 } 623 return isControl; 624} 625 626var requestFrame = (function () { 627 return (window.requestAnimationFrame || 628 window.webkitRequestAnimationFrame || 629 window.mozRequestAnimationFrame || 630 function (callback, element, delay) { 631 window.setTimeout(callback, delay || (1000/60)); 632 }); 633})(); 634 635var MutationObserver = (window.MutationObserver || 636 window.WebKitMutationObserver || 637 window.MozMutationObserver); 638 639var getScrollRoot = (function() { 640 var SCROLL_ROOT; 641 return function() { 642 if (!SCROLL_ROOT) { 643 var dummy = document.createElement('div'); 644 dummy.style.cssText = 'height:10000px;width:1px;'; 645 document.body.appendChild(dummy); 646 var bodyScrollTop = document.body.scrollTop; 647 var docElScrollTop = document.documentElement.scrollTop; 648 window.scrollBy(0, 3); 649 if (document.body.scrollTop != bodyScrollTop) 650 (SCROLL_ROOT = document.body); 651 else 652 (SCROLL_ROOT = document.documentElement); 653 window.scrollBy(0, -3); 654 document.body.removeChild(dummy); 655 } 656 return SCROLL_ROOT; 657 }; 658})(); 659 660 661/*********************************************** 662 * PULSE (by Michael Herf) 663 ***********************************************/ 664 665/** 666 * Viscous fluid with a pulse for part and decay for the rest. 667 * - Applies a fixed force over an interval (a damped acceleration), and 668 * - Lets the exponential bleed away the velocity over a longer interval 669 * - Michael Herf, http://stereopsis.com/stopping/ 670 */ 671function pulse_(x) { 672 var val, start, expx; 673 // test 674 x = x * options.pulseScale; 675 if (x < 1) { // acceleartion 676 val = x - (1 - Math.exp(-x)); 677 } else { // tail 678 // the previous animation ended here: 679 start = Math.exp(-1); 680 // simple viscous drag 681 x -= 1; 682 expx = 1 - Math.exp(-x); 683 val = start + (expx * (1 - start)); 684 }
685 return val * options.pulseNormalize; 686} 687 688function pulse(x) { 689 if (x >= 1) return 1; 690 if (x <= 0) return 0; 691 692 if (options.pulseNormalize == 1) { 693 options.pulseNormalize /= pulse_(1); 694 } 695 return pulse_(x); 696} 697 698 699/*********************************************** 700 * FIRST RUN 701 ***********************************************/ 702 703var userAgent = window.navigator.userAgent; 704var isEdge = /Edge/.test(userAgent); // thank you MS 705var isChrome = /chrome/i.test(userAgent) && !isEdge; 706var isSafari = /safari/i.test(userAgent) && !isEdge; 707var isMobile = /mobile/i.test(userAgent); 708var isIEWin7 = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent); 709var isOldSafari = isSafari && (/Version\/8/i.test(userAgent) || /Version\/9/i.test(userAgent)); 710var isEnabledForBrowser = (isChrome || isSafari || isIEWin7) && !isMobile; 711 712var wheelEvent; 713if ('onwheel' in document.createElement('div')) 714 wheelEvent = 'wheel'; 715else if ('onmousewheel' in document.createElement('div')) 716 wheelEvent = 'mousewheel'; 717 718if (wheelEvent && isEnabledForBrowser) { 719 addEvent(wheelEvent, wheel); 720 addEvent('mousedown', mousedown); 721 addEvent('load', init); 722} 723 724 725/*********************************************** 726 * PUBLIC INTERFACE 727 ***********************************************/ 728 729function SmoothScroll(optionsToSet) { 730 for (var key in optionsToSet) 731 if (defaultOptions.hasOwnProperty(key)) 732 options[key] = optionsToSet[key]; 733} 734SmoothScroll.destroy = cleanup; 735 736if (window.SmoothScrollOptions) // async API 737 SmoothScroll(window.SmoothScrollOptions); 738 739if (typeof define === 'function' && define.amd) 740 define(function() { 741 return SmoothScroll; 742 }); 743else if ('object' == typeof exports) 744 module.exports = SmoothScroll; 745else 746 window.SmoothScroll = SmoothScroll; 747 748})();
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.