PageSourceSearch

https://www.gcl.uk/wp-content/themes/experts/js/SmoothScroll.js?ver=2ed66d4253d61b2eaf83603e7c2d6a95

js gcl.uk collected 2026-10-08 11:01:13 UTC 20,815 bytes, 729 lines download raw bytes

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

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.