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