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