PageSourceSearch

https://www.cop.es/IPsyNet/js/SmoothScroll.js

js cop.es collected 2026-10-02 05:50:26 UTC 14,317 bytes, 523 lines download raw bytes

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

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.