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