PageSourceSearch

https://moratelindo.co.id/js/smooth-scroll/SmoothScroll.js

js moratelindo.co.id collected 2026-10-02 15:52:48 UTC 14,152 bytes, 541 lines download raw bytes

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

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.