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