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