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