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