1// SmoothScroll v1.2.1 2// Licensed under the terms of the MIT license. 3 4// People involved 5// - Balazs Galambosi (maintainer) 6// - Patrick Brunner (original idea) 7// - Michael Herf (Pulse Algorithm) 8 9// Scroll Variables (tweakable) 10var defaultOptions = { 11 12 // Scrolling Core 13 frameRate : 150, // [Hz] 14 animationTime : 600, // [px] 15 stepSize : 120, // [px] 16 17 // Pulse (less tweakable) 18 // ratio of "tail" to "acceleration" 19 pulseAlgorithm : true, 20 pulseScale : 8, 21 pulseNormalize : 1, 22 23 // Acceleration 24 accelerationDelta : 20, // 20 25 accelerationMax : 1, // 1 26 27 // Keyboard Settings 28 keyboardSupport : true, // option 29 arrowScroll : 50, // [px] 30 31 // Other 32 touchpadSupport : true, 33 fixedBackground : true, 34 excluded : "" 35}; 36 37var options = defaultOptions; 38 39 40// Other Variables 41var isExcluded = false; 42var isFrame = false; 43var direction = { x: 0, y: 0 }; 44var initDone = false; 45var root = document.documentElement; 46var activeElement; 47var observer; 48var deltaBuffer = [ 120, 120, 120 ]; 49 50var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, 51 pageup: 33, pagedown: 34, end: 35, home: 36 }; 52 53 54/*********************************************** 55 * SETTINGS 56 ***********************************************/ 57if(typeof(chrome) !== 'undefined' && typeof(chrome.storage) !== 'undefined') { 58chrome.storage.sync.get(defaultOptions, function (syncedOptions) { 59 60 options = syncedOptions; 61 62 // it seems that sometimes settings come late 63 // and we need to test again for excluded pages 64 initTest(); 65}); 66} 67 68/*********************************************** 69 * INITIALIZE 70 ***********************************************/ 71 72/** 73 * Tests if smooth scrolling is allowed. Shuts down everything if not. 74 */ 75function initTest() { 76 77 var disableKeyboard = false; 78 79 // disable keys for google reader (spacebar conflict) 80 if (document.URL.indexOf("google.com/reader/view") > -1) { 81 disableKeyboard = true; 82 } 83 84 // disable everything if the page is blacklisted 85 if (options.excluded) { 86 var domains = options.excluded.split(/[,\n] ?/); 87 domains.push("mail.google.com"); // exclude Gmail for now 88 for (var i = domains.length; i--;) { 89 if (document.URL.indexOf(domains[i]) > -1) { 90 observer && observer.disconnect(); 91 removeEvent("mousewheel", wheel); 92 disableKeyboard = true; 93 isExcluded = true; 94 break; 95 } 96 } 97 } 98 99 // disable keyboard support if anything above requested it 100 if (disableKeyboard) { 101 removeEvent("keydown", keydown); 102 } 103 104 if (options.keyboardSupport && !disableKeyboard) { 105 addEvent("keydown", keydown); 106 } 107} 108 109/** 110 * Sets up scrolls array, determines if frames are involved. 111 */ 112function init() { 113 114 if (!document.body) return; 115 116 var body = document.body; 117 var html = document.documentElement; 118 var windowHeight = window.innerHeight; 119 var scrollHeight = body.scrollHeight; 120 121 // check compat mode for root element 122 root = (document.compatMode.indexOf('CSS') >= 0) ? html : body; 123 activeElement = body; 124 125 initTest(); 126 initDone = true; 127 128 // Checks if this script is running in a frame 129 if (top != self) { 130 isFrame = true; 131 } 132 133 /** 134 * This fixes a bug where the areas left and right to 135 * the content does not trigger the onmousewheel event 136 * on some pages. e.g.: html, body { height: 100% } 137 */ 138 else if (scrollHeight > windowHeight && 139 (body.offsetHeight <= windowHeight || 140 html.offsetHeight <= windowHeight)) { 141 142 // DOMChange (throttle): fix height 143 var pending = false; 144 var refresh = function () { 145 if (!pending && html.scrollHeight != document.height) { 146 pending = true; // add a new pending action 147 setTimeout(function () { 148 html.style.height = document.height + 'px'; 149 pending = false;
150 }, 500); // act rarely to stay fast 151 } 152 }; 153 html.style.height = 'auto'; 154 setTimeout(refresh, 10); 155 156 var config = { 157 attributes: true, 158 childList: true, 159 characterData: false 160 }; 161 162 observer = new MutationObserver(refresh); 163 observer.observe(body, config); 164 165 // clearfix 166 if (root.offsetHeight <= windowHeight) { 167 var underlay = document.createElement("div"); 168 underlay.style.clear = "both"; 169 body.appendChild(underlay); 170 } 171 } 172 173 // gmail performance fix 174 if (document.URL.indexOf("mail.google.com") > -1) { 175 var s = document.createElement("style"); 176 s.innerHTML = ".iu { visibility: hidden }"; 177 (document.getElementsByTagName("head")[0] || html).appendChild(s); 178 } 179 // facebook better home timeline performance 180 // all the HTML resized images make rendering CPU intensive 181 else if (document.URL.indexOf("www.facebook.com") > -1) { 182 var home_stream = document.getElementById("home_stream"); 183 home_stream && (home_stream.style.webkitTransform = "translateZ(0)"); 184 } 185 // disable fixed background 186 if (!options.fixedBackground && !isExcluded) { 187 body.style.backgroundAttachment = "scroll"; 188 html.style.backgroundAttachment = "scroll"; 189 } 190} 191 192 193/************************************************ 194 * SCROLLING 195 ************************************************/ 196 197var que = []; 198var pending = false; 199var lastScroll = +new Date; 200 201/** 202 * Pushes scroll actions to the scrolling queue. 203 */ 204function scrollArray(elem, left, top, delay) { 205 206 delay || (delay = 1000); 207 directionCheck(left, top); 208 209 if (options.accelerationMax != 1) { 210 var now = +new Date; 211 var elapsed = now - lastScroll; 212 if (elapsed < options.accelerationDelta) { 213 var factor = (1 + (30 / elapsed)) / 2; 214 if (factor > 1) { 215 factor = Math.min(factor, options.accelerationMax); 216 left *= factor; 217 top *= factor; 218 } 219 } 220 lastScroll = +new Date; 221 } 222 223 // push a scroll command 224 que.push({ 225 x: left, 226 y: top, 227 lastX: (left < 0) ? 0.99 : -0.99, 228 lastY: (top < 0) ? 0.99 : -0.99, 229 start: +new Date 230 }); 231 232 // don't act if there's a pending queue 233 if (pending) { 234 return; 235 } 236 237 var scrollWindow = (elem === document.body); 238 239 var step = function (time) { 240 241 var now = +new Date; 242 var scrollX = 0; 243 var scrollY = 0; 244 245 for (var i = 0; i < que.length; i++) { 246 247 var item = que[i]; 248 var elapsed = now - item.start; 249 var finished = (elapsed >= options.animationTime); 250 251 // scroll position: [0, 1] 252 var position = (finished) ? 1 : elapsed / options.animationTime; 253 254 // easing [optional] 255 if (options.pulseAlgorithm) { 256 position = pulse(position); 257 } 258 259 // only need the difference 260 var x = (item.x * position - item.lastX) >> 0; 261 var y = (item.y * position - item.lastY) >> 0; 262 263 // add this to the total scrolling 264 scrollX += x; 265 scrollY += y; 266 267 // update last values 268 item.lastX += x; 269 item.lastY += y; 270 271 // delete and step back if it's over 272 if (finished) { 273 que.splice(i, 1); i--; 274 } 275 } 276 277 // scroll left and top 278 if (scrollWindow) { 279 window.scrollBy(scrollX, scrollY); 280 } 281 else { 282 if (scrollX) elem.scrollLeft += scrollX; 283 if (scrollY) elem.scrollTop += scrollY; 284 } 285 286 // clean up if there's nothing left to do 287 if (!left && !top) { 288 que = []; 289 } 290 291 if (que.length) { 292 requestFrame(step, elem, (delay / options.frameRate + 1)); 293 } else { 294 pending = false;
vendor: 4,164 bytes, lines 295-458
295 } 296 }; 297 298 // start a new queue of actions 299 requestFrame(step, elem, 0); 300 pending = true; 301} 302 303 304/*********************************************** 305 * EVENTS 306 ***********************************************/ 307 308/** 309 * Mouse wheel handler. 310 * @param {Object} event 311 */ 312function wheel(event) { 313 314 if (!initDone) { 315 init(); 316 } 317 318 var target = event.target; 319 var overflowing = overflowingAncestor(target); 320 321 // use default if there's no overflowing 322 // element or default action is prevented 323 if (!overflowing || event.defaultPrevented || 324 isNodeName(activeElement, "embed") || 325 (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) { 326 return true; 327 } 328 329 var deltaX = event.wheelDeltaX || 0; 330 var deltaY = event.wheelDeltaY || 0; 331 332 // use wheelDelta if deltaX/Y is not available 333 if (!deltaX && !deltaY) { 334 deltaY = event.wheelDelta || 0; 335 } 336 337 // check if it's a touchpad scroll that should be ignored 338 if (!options.touchpadSupport && isTouchpad(deltaY)) { 339 return true; 340 } 341 342 // scale by step size 343 // delta is 120 most of the time 344 // synaptics seems to send 1 sometimes 345 if (Math.abs(deltaX) > 1.2) { 346 deltaX *= options.stepSize / 120; 347 } 348 if (Math.abs(deltaY) > 1.2) { 349 deltaY *= options.stepSize / 120; 350 } 351 352 scrollArray(overflowing, -deltaX, -deltaY); 353 event.preventDefault(); 354} 355 356/** 357 * Keydown event handler. 358 * @param {Object} event 359 */ 360function keydown(event) { 361 362 var target = event.target; 363 var modifier = event.ctrlKey || event.altKey || event.metaKey || 364 (event.shiftKey && event.keyCode !== key.spacebar); 365 366 // do nothing if user is editing text 367 // or using a modifier key (except shift) 368 // or in a dropdown 369 if ( /input|textarea|select|embed/i.test(target.nodeName) || 370 target.isContentEditable || 371 event.defaultPrevented || 372 modifier ) { 373 return true; 374 } 375 // spacebar should trigger button press 376 if (isNodeName(target, "button") && 377 event.keyCode === key.spacebar) { 378 return true; 379 } 380 381 var shift, x = 0, y = 0; 382 var elem = overflowingAncestor(activeElement); 383 var clientHeight = elem.clientHeight; 384 385 if (elem == document.body) { 386 clientHeight = window.innerHeight; 387 } 388 389 switch (event.keyCode) { 390 case key.up: 391 y = -options.arrowScroll; 392 break; 393 case key.down: 394 y = options.arrowScroll; 395 break; 396 case key.spacebar: // (+ shift) 397 shift = event.shiftKey ? 1 : -1; 398 y = -shift * clientHeight * 0.9; 399 break; 400 case key.pageup: 401 y = -clientHeight * 0.9; 402 break; 403 case key.pagedown: 404 y = clientHeight * 0.9; 405 break; 406 case key.home: 407 y = -elem.scrollTop; 408 break; 409 case key.end: 410 var damt = elem.scrollHeight - elem.scrollTop - clientHeight; 411 y = (damt > 0) ? damt+10 : 0; 412 break; 413 case key.left: 414 x = -options.arrowScroll; 415 break; 416 case key.right: 417 x = options.arrowScroll; 418 break; 419 default: 420 return true; // a key we don't care about 421 } 422 423 scrollArray(elem, x, y); 424 event.preventDefault(); 425} 426 427/** 428 * Mousedown event only for updating activeElement 429 */ 430function mousedown(event) { 431 activeElement = event.target; 432} 433 434 435/*********************************************** 436 * OVERFLOW 437 ***********************************************/ 438 439var cache = {}; // cleared out every once in while 440setInterval(function () { cache = {}; }, 10 * 1000); 441 442var uniqueID = (function () { 443 var i = 0; 444 return function (el) { 445 return el.uniqueID || (el.uniqueID = i++); 446 }; 447})(); 448 449function setCache(elems, overflowing) { 450 for (var i = elems.length; i--;) 451 cache[uniqueID(elems[i])] = overflowing; 452 return overflowing; 453} 454 455function overflowingAncestor(el) { 456 var elems = []; 457 var rootScrollHeight = root.scrollHeight; 458 do {
459 var cached = cache[uniqueID(el)]; 460 if (cached) { 461 return setCache(elems, cached); 462 } 463 elems.push(el); 464 if (rootScrollHeight === el.scrollHeight) { 465 if (!isFrame || root.clientHeight + 10 < rootScrollHeight) { 466 return setCache(elems, document.body); // scrolling root in WebKit 467 } 468 } else if (el.clientHeight + 10 < el.scrollHeight) { 469 overflow = getComputedStyle(el, "").getPropertyValue("overflow-y"); 470 if (overflow === "scroll" || overflow === "auto") { 471 return setCache(elems, el); 472 } 473 } 474 } while (el = el.parentNode); 475} 476 477 478/*********************************************** 479 * HELPERS 480 ***********************************************/ 481 482function addEvent(type, fn, bubble) { 483 window.addEventListener(type, fn, (bubble||false)); 484} 485 486function removeEvent(type, fn, bubble) { 487 window.removeEventListener(type, fn, (bubble||false)); 488} 489 490function isNodeName(el, tag) { 491 return (el.nodeName||"").toLowerCase() === tag.toLowerCase(); 492} 493 494function directionCheck(x, y) { 495 x = (x > 0) ? 1 : -1; 496 y = (y > 0) ? 1 : -1; 497 if (direction.x !== x || direction.y !== y) { 498 direction.x = x; 499 direction.y = y; 500 que = []; 501 lastScroll = 0; 502 } 503} 504 505var deltaBufferTimer; 506 507function isTouchpad(deltaY) { 508 if (!deltaY) return; 509 deltaY = Math.abs(deltaY) 510 deltaBuffer.push(deltaY); 511 deltaBuffer.shift(); 512 clearTimeout(deltaBufferTimer); 513 deltaBufferTimer = setTimeout(function () { 514 chrome.storage.local.set({ deltaBuffer: deltaBuffer }); 515 }, 1000); 516 var allEquals = (deltaBuffer[0] == deltaBuffer[1] && 517 deltaBuffer[1] == deltaBuffer[2]); 518 var allDivisable = (isDivisible(deltaBuffer[0], 120) && 519 isDivisible(deltaBuffer[1], 120) && 520 isDivisible(deltaBuffer[2], 120)); 521 return !(allEquals || allDivisable); 522} 523 524function isDivisible(n, divisor) { 525 return (Math.floor(n / divisor) == n / divisor); 526} 527 528if(typeof(chrome) !== 'undefined' && typeof(chrome.storage) !== 'undefined') { 529chrome.storage.local.get('deltaBuffer', function (stored) { 530 if (stored.deltaBuffer) { 531 deltaBuffer = stored.deltaBuffer; 532 } 533}); 534} 535 536var requestFrame = (function () { 537 return window.requestAnimationFrame || 538 window.webkitRequestAnimationFrame || 539 function (callback, element, delay) { 540 window.setTimeout(callback, delay || (1000/60)); 541 }; 542})(); 543 544var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 545 546 547/*********************************************** 548 * PULSE 549 ***********************************************/ 550 551/** 552 * Viscous fluid with a pulse for part and decay for the rest. 553 * - Applies a fixed force over an interval (a damped acceleration), and 554 * - Lets the exponential bleed away the velocity over a longer interval 555 * - Michael Herf, http://stereopsis.com/stopping/ 556 */ 557function pulse_(x) { 558 var val, start, expx; 559 // test 560 x = x * options.pulseScale; 561 if (x < 1) { // acceleartion 562 val = x - (1 - Math.exp(-x)); 563 } else { // tail 564 // the previous animation ended here: 565 start = Math.exp(-1); 566 // simple viscous drag 567 x -= 1; 568 expx = 1 - Math.exp(-x); 569 val = start + (expx * (1 - start)); 570 } 571 return val * options.pulseNormalize; 572} 573 574function pulse(x) { 575 if (x >= 1) return 1; 576 if (x <= 0) return 0; 577 578 if (options.pulseNormalize == 1) { 579 options.pulseNormalize /= pulse_(1); 580 } 581 return pulse_(x); 582} 583 584addEvent("mousedown", mousedown); 585addEvent("mousewheel", wheel); 586addEvent("load", init);
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.