Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.Services.WebApp/3.3/WebApp/libs/smoothScroll/smoothScroll.js

Last change on this file was 12428, checked in by ascheibe, 10 years ago

#2394 added web app and status page to trunk

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