Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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