PageSourceSearch

https://quakelogic.net/assets/js/vendor/smoothscroll.js

js quakelogic.net collected 2026-10-06 20:36:31 UTC 22,357 bytes, 670 lines download raw bytes

1(function () {
2
3
4    if (navigator.appVersion.indexOf("Win") != -1) {
5
6        // Scroll Variables (tweakable)
7        var defaultOptions = {
8
9            // Scrolling Core
10            frameRate: 150, // [Hz]
11            animationTime: 500, // [px]
12            stepSize: 150, // [px] (120 default)
13
14            // Pulse (less tweakable)
15            // ratio of "tail" to "acceleration"
16            pulseAlgorithm: true,
17            pulseScale: 6, // (8 default)
18            pulseNormalize: 1,
19
20            // Acceleration
21            accelerationDelta: 20,  // 20
22            accelerationMax: 1,   // 1
23
24            // Keyboard Settings
25            keyboardSupport: true,  // option
26            arrowScroll: 50,     // [px]
27
28            // Other
29            touchpadSupport: true,
30            fixedBackground: true,
31            excluded: ''
32        };
33
34        var options = defaultOptions;
35
36
37        // Other Variables
38        var isExcluded = false;
39        var isFrame = false;
40        var direction = {x: 0, y: 0};
41        var initDone = false;
42        var root = document.documentElement;
43        var activeElement;
44        var observer;
45        var deltaBuffer = [];
46        var isMac = /^Mac/.test(navigator.platform);
47
48        var key = {
49            left: 37, up: 38, right: 39, down: 40, spacebar: 32,
50            pageup: 33, pagedown: 34, end: 35, home: 36
51        };
52
53
54        /***********************************************
55         * SETTINGS
56         ***********************************************/
57
58        var options = defaultOptions;
59
60
61        /***********************************************
62         * INITIALIZE
63         ***********************************************/
64
65        /**
66         * Tests if smooth scrolling is allowed. Shuts down everything if not.
67         */
68        function initTest() {
69            if (options.keyboardSupport) {
70                addEvent('keydown', keydown);
71            }
72        }
73
74        /**
75         * Sets up scrolls array, determines if frames are involved.
76         */
77        function init() {
78
79            if (initDone || !document.body) return;
80
81            initDone = true;
82
83            var body = document.body;
84            var html = document.documentElement;
85            var windowHeight = window.innerHeight;
86            var scrollHeight = body.scrollHeight;
87
88            // check compat mode for root element
89            root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
90            activeElement = body;
91
92            initTest();
93
94            // Checks if this script is running in a frame
95            if (top != self) {
96                isFrame = true;
97            }
98
99            /**
100             * This fixes a bug where the areas left and right to
101             * the content does not trigger the onmousewheel event
102             * on some pages. e.g.: html, body { height: 100% }
103             */
104            else if (scrollHeight > windowHeight &&
105                (body.offsetHeight <= windowHeight ||
106                    html.offsetHeight <= windowHeight)) {
107
108                // DOMChange (throttle): fix height
109                var pending = false;
110                var refresh = function () {
111                    if (!pending && html.scrollHeight != document.height) {
112                        pending = true; // add a new pending action
113                        setTimeout(function () {
114                            html.style.height = document.height + 'px';
115                            pending = false;
116                        }, 500); // act rarely to stay fast
117                    }
118                };
119
120                html.style.oldHeight = html.style.height;
121                html.style.height = 'auto';
122
123                setTimeout(refresh, 10);
124
125                var config = {
126                    attributes: true,
127                    childList: true,
128                    characterData: false
129                };
130
131                observer = new MutationObserver(refresh);
132                observer.observe(body, config);
133
134                // clearfix
135                if (root.offsetHeight <= windowHeight) {
136                    var underlay = document.createElement('div');
137                    underlay.style.clear = 'both';
vendor: 6,356 bytes, lines 138-333
138                    body.appendChild(underlay);
139                }
140            }
141
142            // disable fixed background
143            if (!options.fixedBackground && !isExcluded) {
144                body.style.backgroundAttachment = 'scroll';
145                html.style.backgroundAttachment = 'scroll';
146            }
147        }
148
149        /**
150         * Removes event listeners and other traces left on the page.
151         */
152        function cleanup() {
153            observer && observer.disconnect();
154            removeEvent(wheelEvent, wheel);
155            removeEvent('mousedown', mousedown);
156            removeEvent('keydown', keydown);
157            var html = document.documentElement;
158            html.style.height = html.style.oldHeight;
159        }
160
161
162        /************************************************
163         * SCROLLING
164         ************************************************/
165
166        var que = [];
167        var pending = false;
168        var lastScroll = Date.now();
169
170        /**
171         * Pushes scroll actions to the scrolling queue.
172         */
173        function scrollArray(elem, left, top, delay) {
174
175            delay || (delay = 1000);
176            directionCheck(left, top);
177
178            if (options.accelerationMax != 1) {
179                var now = Date.now();
180                var elapsed = now - lastScroll;
181                if (elapsed < options.accelerationDelta) {
182                    var factor = (1 + (50 / elapsed)) / 2;
183                    if (factor > 1) {
184                        factor = Math.min(factor, options.accelerationMax);
185                        left *= factor;
186                        top *= factor;
187                    }
188                }
189                lastScroll = Date.now();
190            }
191
192            // push a scroll command
193            que.push({
194                x: left,
195                y: top,
196                lastX: (left < 0) ? 0.99 : -0.99,
197                lastY: (top < 0) ? 0.99 : -0.99,
198                start: Date.now()
199            });
200
201            // don't act if there's a pending queue
202            if (pending) {
203                return;
204            }
205
206            var scrollWindow = (elem === document.body);
207
208            var step = function (time) {
209
210                var now = Date.now();
211                var scrollX = 0;
212                var scrollY = 0;
213
214                for (var i = 0; i < que.length; i++) {
215
216                    var item = que[i];
217                    var elapsed = now - item.start;
218                    var finished = (elapsed >= options.animationTime);
219
220                    // scroll position: [0, 1]
221                    var position = (finished) ? 1 : elapsed / options.animationTime;
222
223                    // easing [optional]
224                    if (options.pulseAlgorithm) {
225                        position = pulse(position);
226                    }
227
228                    // only need the difference
229                    var x = (item.x * position - item.lastX) >> 0;
230                    var y = (item.y * position - item.lastY) >> 0;
231
232                    // add this to the total scrolling
233                    scrollX += x;
234                    scrollY += y;
235
236                    // update last values
237                    item.lastX += x;
238                    item.lastY += y;
239
240                    // delete and step back if it's over
241                    if (finished) {
242                        que.splice(i, 1);
243                        i--;
244                    }
245                }
246
247                // scroll left and top
248                if (scrollWindow) {
249                    window.scrollBy(scrollX, scrollY);
250                } else {
251                    if (scrollX) elem.scrollLeft += scrollX;
252                    if (scrollY) elem.scrollTop += scrollY;
253                }
254
255                // clean up if there's nothing left to do
256                if (!left && !top) {
257                    que = [];
258                }
259
260                if (que.length) {
261                    requestFrame(step, elem, (delay / options.frameRate + 1));
262                } else {
263                    pending = false;
264                }
265            };
266
267            // start a new queue of actions
268            requestFrame(step, elem, 0);
269            pending = true;
270        }
271
272
273        /***********************************************
274         * EVENTS
275         ***********************************************/
276
277        /**
278         * Mouse wheel handler.
279         * @param {Object} event
280         */
281        function wheel(event) {
282
283            if (!initDone) {
284                init();
285            }
286
287            var target = event.target;
288            var overflowing = overflowingAncestor(target);
289
290            // use default if there's no overflowing
291            // element or default action is prevented   
292            // or it's a zooming event with CTRL 
293            if (!overflowing || event.defaultPrevented || event.ctrlKey) {
294                return true;
295            }
296
297            // leave embedded content alone (flash & pdf)
298            if (isNodeName(activeElement, 'embed') ||
299                (isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) ||
300                isNodeName(activeElement, 'object')) {
301                return true;
302            }
303
304            var deltaX = -event.wheelDeltaX || event.deltaX || 0;
305            var deltaY = -event.wheelDeltaY || event.deltaY || 0;
306
307            if (isMac) {
308                if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) {
309                    deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX));
310                }
311                if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) {
312                    deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY));
313                }
314            }
315
316            // use wheelDelta if deltaX/Y is not available
317            if (!deltaX && !deltaY) {
318                deltaY = -event.wheelDelta || 0;
319            }
320
321            // line based scrolling (Firefox mostly)
322            if (event.deltaMode === 1) {
323                deltaX *= 40;
324                deltaY *= 40;
325            }
326
327            // check if it's a touchpad scroll that should be ignored
328            if (!options.touchpadSupport && isTouchpad(deltaY)) {
329                return true;
330            }
331
332            // scale by step size
333            // delta is 120 most of the time
334            // synaptics seems to send 1 sometimes
335            if (Math.abs(deltaX) > 1.2) {
336                deltaX *= options.stepSize / 120;
337            }
338            if (Math.abs(deltaY) > 1.2) {
339                deltaY *= options.stepSize / 120;
340            }
341
342            scrollArray(overflowing, deltaX, deltaY);
343            event.preventDefault();
344            scheduleClearCache();
345        }
346
347        /**
348         * Keydown event handler.
349         * @param {Object} event
350         */
351        function keydown(event) {
352
353            var target = event.target;
354            var modifier = event.ctrlKey || event.altKey || event.metaKey ||
355                (event.shiftKey && event.keyCode !== key.spacebar);
356
357            // do nothing if user is editing text
358            // or using a modifier key (except shift)
359            // or in a dropdown
360            // or inside interactive elements
361            var inputNodeNames = /^(textarea|select|embed|object)$/i;
362            var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i;
363            if (inputNodeNames.test(target.nodeName) ||
364                isNodeName(target, 'input') && !buttonTypes.test(target.type) ||
365                isNodeName(activeElement, 'video') ||
366                isInsideYoutubeVideo(event) ||
367                target.isContentEditable ||
368                event.defaultPrevented ||
369                modifier) {
370                return true;
371            }
372
373            // spacebar should trigger button press
374            if (isNodeName(target, 'button') &&
375                event.keyCode === key.spacebar) {
376                return true;
377            }
378
379            var shift, x = 0, y = 0;
380            var elem = overflowingAncestor(activeElement);
381            var clientHeight = elem.clientHeight;
382
383            if (elem == document.body) {
384                clientHeight = window.innerHeight;
385            }
386
387            switch (event.keyCode) {
388                case key.up:
389                    y = -options.arrowScroll;
390                    break;
391                case key.down:
392                    y = options.arrowScroll;
393                    break;
394                case key.spacebar: // (+ shift)
395                    shift = event.shiftKey ? 1 : -1;
396                    y = -shift * clientHeight * 0.9;
397                    break;
398                case key.pageup:
399                    y = -clientHeight * 0.9;
400                    break;
401                case key.pagedown:
402                    y = clientHeight * 0.9;
403                    break;
404                case key.home:
405                    y = -elem.scrollTop;
406                    break;
407                case key.end:
408                    var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
409                    y = (damt > 0) ? damt + 10 : 0;
410                    break;
411                case key.left:
412                    x = -options.arrowScroll;
413                    break;
414                case key.right:
415                    x = options.arrowScroll;
416                    break;
417                default:
418                    return true; // a key we don't care about
419            }
420
421            scrollArray(elem, x, y);
422            event.preventDefault();
423            scheduleClearCache();
424        }
425
426        /**
427         * Mousedown event only for updating activeElement
428         */
429        function mousedown(event) {
430            activeElement = event.target;
431        }
432
433
434        /***********************************************
435         * OVERFLOW
436         ***********************************************/
437
438        var uniqueID = (function () {
439            var i = 0;
440            return function (el) {
441                return el.uniqueID || (el.uniqueID = i++);
442            };
443        })();
444
445        var cache = {}; // cleared out after a scrolling session
446        var clearCacheTimer;
447
448        //setInterval(function () { cache = {}; }, 10 * 1000);
449
450        function scheduleClearCache() {
451            clearTimeout(clearCacheTimer);
452            clearCacheTimer = setInterval(function () {
453                cache = {};
454            }, 1 * 1000);
455        }
456
457        function setCache(elems, overflowing) {
458            for (var i = elems.length; i--;)
459                cache[uniqueID(elems[i])] = overflowing;
460            return overflowing;
461        }
462
463        //  (body)                (root)
464        //         | hidden | visible | scroll |  auto  |
465        // hidden  |   no   |    no   |   YES  |   YES  |
466        // visible |   no   |   YES   |   YES  |   YES  |
467        // scroll  |   no   |   YES   |   YES  |   YES  |
468        // auto    |   no   |   YES   |   YES  |   YES  |
469
470        function overflowingAncestor(el) {
471            var elems = [];
472            var body = document.body;
473            var rootScrollHeight = root.scrollHeight;
474            do {
475                var cached = cache[uniqueID(el)];
476                if (cached) {
477                    return setCache(elems, cached);
478                }
479                elems.push(el);
480                if (rootScrollHeight === el.scrollHeight) {
481                    var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body);
482                    var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root);
483                    if (isFrame && isContentOverflowing(root) ||
484                        !isFrame && isOverflowCSS) {
485                        return setCache(elems, getScrollRoot());
486                    }
487                } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) {
488                    return setCache(elems, el);
489                }
490            } while (el = el.parentElement);
491        }
492
493        function isContentOverflowing(el) {
494            return (el.clientHeight + 10 < el.scrollHeight);
495        }
496
497        // typically for <body> and <html>
498        function overflowNotHidden(el) {
499            var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
500            return (overflow !== 'hidden');
501        }
502
503        // for all other elements
504        function overflowAutoOrScroll(el) {
505            var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
506            return (overflow === 'scroll' || overflow === 'auto');
507        }
508
509
510        /***********************************************
511         * HELPERS
512         ***********************************************/
513
514        function addEvent(type, fn, bubble) {
515            window.addEventListener(type, fn, (bubble || false));
516        }
517
518        function removeEvent(type, fn, bubble) {
519            window.removeEventListener(type, fn, (bubble || false));
520        }
521
522        function isNodeName(el, tag) {
523            return (el.nodeName || '').toLowerCase() === tag.toLowerCase();
524        }
525
526        function directionCheck(x, y) {
527            x = (x > 0) ? 1 : -1;
528            y = (y > 0) ? 1 : -1;
529            if (direction.x !== x || direction.y !== y) {
530                direction.x = x;
531                direction.y = y;
532                que = [];
533                lastScroll = 0;
534            }
535        }
536
537        var deltaBufferTimer;
538
539        if (window.localStorage && localStorage.SS_deltaBuffer) {
540            deltaBuffer = localStorage.SS_deltaBuffer.split(',');
541        }
542
543        function isTouchpad(deltaY) {
544            if (!deltaY) return;
545            if (!deltaBuffer.length) {
546                deltaBuffer = [deltaY, deltaY, deltaY];
547            }
548            deltaY = Math.abs(deltaY)
549            deltaBuffer.push(deltaY);
550            deltaBuffer.shift();
551            clearTimeout(deltaBufferTimer);
552            deltaBufferTimer = setTimeout(function () {
553                if (window.localStorage) {
554                    localStorage.SS_deltaBuffer = deltaBuffer.join(',');
555                }
556            }, 1000);
557            return !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100);
558        }
559
560        function isDivisible(n, divisor) {
561            return (Math.floor(n / divisor) == n / divisor);
562        }
563
564        function allDeltasDivisableBy(divisor) {
565            return (isDivisible(deltaBuffer[0], divisor) &&
566                isDivisible(deltaBuffer[1], divisor) &&
567                isDivisible(deltaBuffer[2], divisor));
568        }
569
570        function isInsideYoutubeVideo(event) {
571            var elem = event.target;
572            var isControl = false;
vendor: 3,455 bytes, lines 573-670
573            if (document.URL.indexOf('www.youtube.com/watch') != -1) {
574                do {
575                    isControl = (elem.classList &&
576                        elem.classList.contains('html5-video-controls'));
577                    if (isControl) break;
578                } while (elem = elem.parentNode);
579            }
580            return isControl;
581        }
582
583        var requestFrame = (function () {
584            return (window.requestAnimationFrame ||
585                window.webkitRequestAnimationFrame ||
586                window.mozRequestAnimationFrame ||
587                function (callback, element, delay) {
588                    window.setTimeout(callback, delay || (1000 / 60));
589                });
590        })();
591
592        var MutationObserver = (window.MutationObserver ||
593            window.WebKitMutationObserver ||
594            window.MozMutationObserver);
595
596        var getScrollRoot = (function () {
597            var SCROLL_ROOT;
598            return function () {
599                if (!SCROLL_ROOT) {
600                    var dummy = document.createElement('div');
601                    dummy.style.cssText = 'height:10000px;width:1px;';
602                    document.body.appendChild(dummy);
603                    var bodyScrollTop = document.body.scrollTop;
604                    var docElScrollTop = document.documentElement.scrollTop;
605                    window.scrollBy(0, 1);
606                    if (document.body.scrollTop != bodyScrollTop)
607                        (SCROLL_ROOT = document.body);
608                    else
609                        (SCROLL_ROOT = document.documentElement);
610                    window.scrollBy(0, -1);
611                    document.body.removeChild(dummy);
612                }
613                return SCROLL_ROOT;
614            };
615        })();
616
617
618        /***********************************************
619         * PULSE (by Michael Herf)
620         ***********************************************/
621
622        /**
623         * Viscous fluid with a pulse for part and decay for the rest.
624         * - Applies a fixed force over an interval (a damped acceleration), and
625         * - Lets the exponential bleed away the velocity over a longer interval
626         * - Michael Herf, http://stereopsis.com/stopping/
627         */
628        function pulse_(x) {
629            var val, start, expx;
630            // test
631            x = x * options.pulseScale;
632            if (x < 1) { // acceleartion
633                val = x - (1 - Math.exp(-x));
634            } else {     // tail
635                // the previous animation ended here:
636                start = Math.exp(-1);
637                // simple viscous drag
638                x -= 1;
639                expx = 1 - Math.exp(-x);
640                val = start + (expx * (1 - start));
641            }
642            return val * options.pulseNormalize;
643        }
644
645        function pulse(x) {
646            if (x >= 1) return 1;
647            if (x <= 0) return 0;
648
649            if (options.pulseNormalize == 1) {
650                options.pulseNormalize /= pulse_(1);
651            }
652            return pulse_(x);
653        }
654
655        var wheelEvent;
656        if ('onwheel' in document.createElement('div'))
657            wheelEvent = 'wheel';
658        else if ('onmousewheel' in document.createElement('div'))
659            wheelEvent = 'mousewheel';
660
661        if (wheelEvent) {
662            addEvent(wheelEvent, wheel);
663            addEvent('mousedown', mousedown);
664            addEvent('load', init);
665        }
666
667    }
668
669
670})();

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.