PageSourceSearch

https://southernconnector.com/assets/smooth-scroll/smooth-scroll.js

js southernconnector.com collected 2026-10-07 23:22:42 UTC 21,533 bytes, 750 lines download raw bytes

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

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.