PageSourceSearch

https://fundacion.mtc.es/plugins/SmoothScroll.js

js mtc.es collected 2026-10-02 11:05:41 UTC 20,657 bytes, 549 lines download raw bytes

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.