PageSourceSearch

https://www.charentaisesdeluxe.com/module/MonSite/LBJ/assets/smooth-scroll/SmoothScroll.js

js charentaisesdeluxe.com collected 2026-09-28 15:35:01 UTC 20,326 bytes, 717 lines download raw bytes

1//
2// SmoothScroll for websites v1.4.0 (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 };
63
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,162 bytes, lines 158-347
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        return true;
316    }
317
318    var deltaX = -event.wheelDeltaX || event.deltaX || 0;
319    var deltaY = -event.wheelDeltaY || event.deltaY || 0;
320    
321    if (isMac) {
322        if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) {
323            deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX));
324        }
325        if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) {
326            deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY));
327        }
328    }
329    
330    // use wheelDelta if deltaX/Y is not available
331    if (!deltaX && !deltaY) {
332        deltaY = -event.wheelDelta || 0;
333    }
334
335    // line based scrolling (Firefox mostly)
336    if (event.deltaMode === 1) {
337        deltaX *= 40;
338        deltaY *= 40;
339    }
340    
341    // check if it's a touchpad scroll that should be ignored
342    if (!options.touchpadSupport && isTouchpad(deltaY)) {
343        return true;
344    }
345
346    // scale by step size
347    // delta is 120 most of the time
348    // synaptics seems to send 1 sometimes
349    if (Math.abs(deltaX) > 1.2) {
350        deltaX *= options.stepSize / 120;
351    }
352    if (Math.abs(deltaY) > 1.2) {
353        deltaY *= options.stepSize / 120;
354    }
355    
356    scrollArray(overflowing, deltaX, deltaY);
357    event.preventDefault();
358    scheduleClearCache();
359}
360
361/**
362 * Keydown event handler.
363 * @param {Object} event
364 */
365function keydown(event) {
366
367    var target   = event.target;
368    var modifier = event.ctrlKey || event.altKey || event.metaKey || 
369                  (event.shiftKey && event.keyCode !== key.spacebar);
370    
371    // our own tracked active element could've been removed from the DOM
372    if (!document.contains(activeElement)) {
373        activeElement = document.activeElement;
374    }
375
376    // do nothing if user is editing text
377    // or using a modifier key (except shift)
378    // or in a dropdown
379    // or inside interactive elements
380    var inputNodeNames = /^(textarea|select|embed|object)$/i;
381    var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i;
382    if ( inputNodeNames.test(target.nodeName) ||
383         isNodeName(target, 'input') && !buttonTypes.test(target.type) ||
384         isNodeName(activeElement, 'video') ||
385         isInsideYoutubeVideo(event) ||
386         target.isContentEditable || 
387         event.defaultPrevented   ||
388         modifier ) {
389      return true;
390    }
391    
392    // spacebar should trigger button press
393    if ((isNodeName(target, 'button') ||
394         isNodeName(target, 'input') && buttonTypes.test(target.type)) &&
395        event.keyCode === key.spacebar) {
396      return true;
397    }
398    
399    var shift, x = 0, y = 0;
400    var elem = overflowingAncestor(activeElement);
401    var clientHeight = elem.clientHeight;
402
403    if (elem == document.body) {
404        clientHeight = window.innerHeight;
405    }
406
407    switch (event.keyCode) {
408        case key.up:
409            y = -options.arrowScroll;
410            break;
411        case key.down:
412            y = options.arrowScroll;
413            break;         
414        case key.spacebar: // (+ shift)
415            shift = event.shiftKey ? 1 : -1;
416            y = -shift * clientHeight * 0.9;
417            break;
418        case key.pageup:
419            y = -clientHeight * 0.9;
420            break;
421        case key.pagedown:
422            y = clientHeight * 0.9;
423            break;
424        case key.home:
425            y = -elem.scrollTop;
426            break;
427        case key.end:
428            var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
429            y = (damt > 0) ? damt+10 : 0;
430            break;
431        case key.left:
432            x = -options.arrowScroll;
433            break;
434        case key.right:
435            x = options.arrowScroll;
436            break;            
437        default:
438            return true; // a key we don't care about
439    }
440
441    scrollArray(elem, x, y);
442    event.preventDefault();
443    scheduleClearCache();
444}
445
446/**
447 * Mousedown event only for updating activeElement
448 */
449function mousedown(event) {
450    activeElement = event.target;
451}
452
453
454/***********************************************
455 * OVERFLOW
456 ***********************************************/
457
458var uniqueID = (function () {
459    var i = 0;
460    return function (el) {
461        return el.uniqueID || (el.uniqueID = i++);
462    };
463})();
464
465var cache = {}; // cleared out after a scrolling session
466var clearCacheTimer;
467
468//setInterval(function () { cache = {}; }, 10 * 1000);
469
470function scheduleClearCache() {
471    clearTimeout(clearCacheTimer);
472    clearCacheTimer = setInterval(function () { cache = {}; }, 1*1000);
473}
474
475function setCache(elems, overflowing) {
476    for (var i = elems.length; i--;)
477        cache[uniqueID(elems[i])] = overflowing;
478    return overflowing;
479}
480
481//  (body)                (root)
482//         | hidden | visible | scroll |  auto  |
483// hidden  |   no   |    no   |   YES  |   YES  |
484// visible |   no   |   YES   |   YES  |   YES  |
485// scroll  |   no   |   YES   |   YES  |   YES  |
486// auto    |   no   |   YES   |   YES  |   YES  |
487
488function overflowingAncestor(el) {
489    var elems = [];
490    var body = document.body;
491    var rootScrollHeight = root.scrollHeight;
492    do {
493        var cached = cache[uniqueID(el)];
494        if (cached) {
495            return setCache(elems, cached);
496        }
497        elems.push(el);
498        if (rootScrollHeight === el.scrollHeight) {
499            var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body);
500            var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root);
501            if (isFrame && isContentOverflowing(root) || 
502               !isFrame && isOverflowCSS) {
503                return setCache(elems, getScrollRoot()); 
504            }
505        } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) {
506            return setCache(elems, el);
507        }
508    } while (el = el.parentElement);
509}
510
511function isContentOverflowing(el) {
512    return (el.clientHeight + 10 < el.scrollHeight);
513}
514
515// typically for <body> and <html>
516function overflowNotHidden(el) {
517    var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
518    return (overflow !== 'hidden');
519}
520
521// for all other elements
522function overflowAutoOrScroll(el) {
523    var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
524    return (overflow === 'scroll' || overflow === 'auto');
525}
526
527
528/***********************************************
529 * HELPERS
530 ***********************************************/
531
532function addEvent(type, fn) {
533    window.addEventListener(type, fn, false);
534}
535
536function removeEvent(type, fn) {
537    window.removeEventListener(type, fn, false);  
538}
539
540function isNodeName(el, tag) {
541    return (el.nodeName||'').toLowerCase() === tag.toLowerCase();
542}
543
544function directionCheck(x, y) {
545    x = (x > 0) ? 1 : -1;
546    y = (y > 0) ? 1 : -1;
547    if (direction.x !== x || direction.y !== y) {
548        direction.x = x;
549        direction.y = y;
550        que = [];
551        lastScroll = 0;
552    }
553}
554
555var deltaBufferTimer;
556
557if (window.localStorage && localStorage.SS_deltaBuffer) {
558    deltaBuffer = localStorage.SS_deltaBuffer.split(',');
559}
560
561function isTouchpad(deltaY) {
562    if (!deltaY) return;
563    if (!deltaBuffer.length) {
564        deltaBuffer = [deltaY, deltaY, deltaY];
565    }
566    deltaY = Math.abs(deltaY)
567    deltaBuffer.push(deltaY);
568    deltaBuffer.shift();
569    clearTimeout(deltaBufferTimer);
570    deltaBufferTimer = setTimeout(function () {
571        if (window.localStorage) {
572            localStorage.SS_deltaBuffer = deltaBuffer.join(',');
573        }
574    }, 1000);
575    return !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100);
576} 
577
578function isDivisible(n, divisor) {
579    return (Math.floor(n / divisor) == n / divisor);
580}
581
582function allDeltasDivisableBy(divisor) {
583    return (isDivisible(deltaBuffer[0], divisor) &&
584            isDivisible(deltaBuffer[1], divisor) &&
585            isDivisible(deltaBuffer[2], divisor));
586}
587
588function isInsideYoutubeVideo(event) {
589    var elem = event.target;
590    var isControl = false;
591    if (document.URL.indexOf ('www.youtube.com/watch') != -1) {
592        do {
593            isControl = (elem.classList && 
594                         elem.classList.contains('html5-video-controls'));
595            if (isControl) break;
596        } while (elem = elem.parentNode);
597    }
598    return isControl;
599}
600
601var requestFrame = (function () {
602      return (window.requestAnimationFrame       || 
603              window.webkitRequestAnimationFrame || 
604              window.mozRequestAnimationFrame    ||
605              function (callback, element, delay) {
606                 window.setTimeout(callback, delay || (1000/60));
607             });
608})();
609
610var MutationObserver = (window.MutationObserver || 
611                        window.WebKitMutationObserver ||
612                        window.MozMutationObserver);  
613
614var getScrollRoot = (function() {
615  var SCROLL_ROOT;
616  return function() {
617    if (!SCROLL_ROOT) {
618      var dummy = document.createElement('div');
619      dummy.style.cssText = 'height:10000px;width:1px;';
620      document.body.appendChild(dummy);
621      var bodyScrollTop  = document.body.scrollTop;
622      var docElScrollTop = document.documentElement.scrollTop;
623      window.scrollBy(0, 1);
624      if (document.body.scrollTop != bodyScrollTop)
625        (SCROLL_ROOT = document.body);
626      else 
627        (SCROLL_ROOT = document.documentElement);
628      window.scrollBy(0, -1);
629      document.body.removeChild(dummy);
630    }
631    return SCROLL_ROOT;
632  };
633})();
634
635
636/***********************************************
637 * PULSE (by Michael Herf)
638 ***********************************************/
639 
640/**
641 * Viscous fluid with a pulse for part and decay for the rest.
642 * - Applies a fixed force over an interval (a damped acceleration), and
643 * - Lets the exponential bleed away the velocity over a longer interval
644 * - Michael Herf, http://stereopsis.com/stopping/
645 */
646function pulse_(x) {
647    var val, start, expx;
648    // test
649    x = x * options.pulseScale;
650    if (x < 1) { // acceleartion
651        val = x - (1 - Math.exp(-x));
652    } else {     // tail
653        // the previous animation ended here:
654        start = Math.exp(-1);
655        // simple viscous drag
656        x -= 1;
657        expx = 1 - Math.exp(-x);
658        val = start + (expx * (1 - start));
659    }
660    return val * options.pulseNormalize;
661}
662
663function pulse(x) {
664    if (x >= 1) return 1;
665    if (x <= 0) return 0;
666
667    if (options.pulseNormalize == 1) {
668        options.pulseNormalize /= pulse_(1);
669    }
670    return pulse_(x);
671}
672
673
674/***********************************************
675 * FIRST RUN
676 ***********************************************/
677
678var userAgent = window.navigator.userAgent;
679var isEdge    = /Edge/.test(userAgent); // thank you MS
680var isChrome  = /chrome/i.test(userAgent) && !isEdge; 
681var isSafari  = /safari/i.test(userAgent) && !isEdge; 
682var isMobile  = /mobile/i.test(userAgent);
683var isEnabledForBrowser = (isChrome || isSafari) && !isMobile;
684
685var wheelEvent;
686if ('onwheel' in document.createElement('div'))
687    wheelEvent = 'wheel';
688else if ('onmousewheel' in document.createElement('div'))
689    wheelEvent = 'mousewheel';
690
691if (wheelEvent && isEnabledForBrowser) {
692    addEvent(wheelEvent, wheel);
693    addEvent('mousedown', mousedown);
694    addEvent('load', init);
695}
696
697
698/***********************************************
699 * PUBLIC INTERFACE
700 ***********************************************/
701
702function SmoothScroll(optionsToSet) {
703    for (var key in optionsToSet)
704        if (defaultOptions.hasOwnProperty(key)) 
705            options[key] = optionsToSet[key];
706}
707SmoothScroll.destroy = cleanup;
708
709if (window.SmoothScrollOptions) // async API
710    SmoothScroll(window.SmoothScrollOptions)
711
712if ('object' == typeof exports)
713    module.exports = SmoothScroll;
714else
715    window.SmoothScroll = SmoothScroll;
716
717})();

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.