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