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