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