PageSourceSearch

https://times-info.net/monthly/common/js/smooth-scroll.js

js times-info.net collected 2026-10-01 10:44:52 UTC 12,451 bytes, 442 lines download raw bytes

vendor: 10,409 bytes, lines 1-374
1/* smoothscroll v0.4.4 - 2019 - Dustan Kasten, Jeremias Menichelli - MIT License */
2// polyfill
3function smoothScrollPolyfill() {
4  // aliases
5  var w = window;
6  var d = document;
7
8  // return if scroll behavior is supported and polyfill is not forced
9  if (
10    'scrollBehavior' in d.documentElement.style &&
11    w.__forceSmoothScrollPolyfill__ !== true
12  ) {
13    return;
14  }
15
16  // globals
17  var Element = w.HTMLElement || w.Element;
18  var SCROLL_TIME = 468;
19
20  // object gathering original scroll methods
21  var original = {
22    scroll: w.scroll || w.scrollTo,
23    scrollBy: w.scrollBy,
24    elementScroll: Element.prototype.scroll || scrollElement,
25    scrollIntoView: Element.prototype.scrollIntoView
26  };
27
28  // define timing method
29  var now =
30    w.performance && w.performance.now
31      ? w.performance.now.bind(w.performance)
32      : Date.now;
33
34  /**
35   * indicates if a the current browser is made by Microsoft
36   * @method isMicrosoftBrowser
37   * @param {String} userAgent
38   * @returns {Boolean}
39   */
40  function isMicrosoftBrowser(userAgent) {
41    var userAgentPatterns = ['MSIE ', 'Trident/', 'Edge/'];
42
43    return new RegExp(userAgentPatterns.join('|')).test(userAgent);
44  }
45
46  /*
47   * IE has rounding bug rounding down clientHeight and clientWidth and
48   * rounding up scrollHeight and scrollWidth causing false positives
49   * on hasScrollableSpace
50   */
51  var ROUNDING_TOLERANCE = isMicrosoftBrowser(w.navigator.userAgent) ? 1 : 0;
52
53  /**
54   * changes scroll position inside an element
55   * @method scrollElement
56   * @param {Number} x
57   * @param {Number} y
58   * @returns {undefined}
59   */
60  function scrollElement(x, y) {
61    this.scrollLeft = x;
62    this.scrollTop = y;
63  }
64
65  /**
66   * returns result of applying ease math function to a number
67   * @method ease
68   * @param {Number} k
69   * @returns {Number}
70   */
71  function ease(k) {
72    return 0.5 * (1 - Math.cos(Math.PI * k));
73  }
74
75  /**
76   * indicates if a smooth behavior should be applied
77   * @method shouldBailOut
78   * @param {Number|Object} firstArg
79   * @returns {Boolean}
80   */
81  function shouldBailOut(firstArg) {
82    if (
83      firstArg === null ||
84      typeof firstArg !== 'object' ||
85      firstArg.behavior === undefined ||
86      firstArg.behavior === 'auto' ||
87      firstArg.behavior === 'instant'
88    ) {
89      // first argument is not an object/null
90      // or behavior is auto, instant or undefined
91      return true;
92    }
93
94    if (typeof firstArg === 'object' && firstArg.behavior === 'smooth') {
95      // first argument is an object and behavior is smooth
96      return false;
97    }
98
99    // throw error when behavior is not supported
100    throw new TypeError(
101      'behavior member of ScrollOptions ' +
102      firstArg.behavior +
103      ' is not a valid value for enumeration ScrollBehavior.'
104    );
105  }
106
107  /**
108   * indicates if an element has scrollable space in the provided axis
109   * @method hasScrollableSpace
110   * @param {Node} el
111   * @param {String} axis
112   * @returns {Boolean}
113   */
114  function hasScrollableSpace(el, axis) {
115    if (axis === 'Y') {
116      return el.clientHeight + ROUNDING_TOLERANCE < el.scrollHeight;
117    }
118
119    if (axis === 'X') {
120      return el.clientWidth + ROUNDING_TOLERANCE < el.scrollWidth;
121    }
122  }
123
124  /**
125   * indicates if an element has a scrollable overflow property in the axis
126   * @method canOverflow
127   * @param {Node} el
128   * @param {String} axis
129   * @returns {Boolean}
130   */
131  function canOverflow(el, axis) {
132    var overflowValue = w.getComputedStyle(el, null)['overflow' + axis];
133
134    return overflowValue === 'auto' || overflowValue === 'scroll';
135  }
136
137  /**
138   * indicates if an element can be scrolled in either axis
139   * @method isScrollable
140   * @param {Node} el
141   * @param {String} axis
142   * @returns {Boolean}
143   */
144  function isScrollable(el) {
145    var isScrollableY = hasScrollableSpace(el, 'Y') && canOverflow(el, 'Y');
146    var isScrollableX = hasScrollableSpace(el, 'X') && canOverflow(el, 'X');
147
148    return isScrollableY || isScrollableX;
149  }
150
151  /**
152   * finds scrollable parent of an element
153   * @method findScrollableParent
154   * @param {Node} el
155   * @returns {Node} el
156   */
157  function findScrollableParent(el) {
158    while (el !== d.body && isScrollable(el) === false) {
159      el = el.parentNode || el.host;
160    }
161
162    return el;
163  }
164
165  /**
166   * self invoked function that, given a context, steps through scrolling
167   * @method step
168   * @param {Object} context
169   * @returns {undefined}
170   */
171  function step(context) {
172    var time = now();
173    var value;
174    var currentX;
175    var currentY;
176    var elapsed = (time - context.startTime) / SCROLL_TIME;
177
178    // avoid elapsed times higher than one
179    elapsed = elapsed > 1 ? 1 : elapsed;
180
181    // apply easing to elapsed time
182    value = ease(elapsed);
183
184    currentX = context.startX + (context.x - context.startX) * value;
185    currentY = context.startY + (context.y - context.startY) * value;
186
187    context.method.call(context.scrollable, currentX, currentY);
188
189    // scroll more if we have not reached our destination
190    if (currentX !== context.x || currentY !== context.y) {
191      w.requestAnimationFrame(step.bind(w, context));
192    }
193  }
194
195  /**
196   * scrolls window or element with a smooth behavior
197   * @method smoothScroll
198   * @param {Object|Node} el
199   * @param {Number} x
200   * @param {Number} y
201   * @returns {undefined}
202   */
203  function smoothScroll(el, x, y) {
204    var scrollable;
205    var startX;
206    var startY;
207    var method;
208    var startTime = now();
209
210    // define scroll context
211    if (el === d.body) {
212      scrollable = w;
213      startX = w.scrollX || w.pageXOffset;
214      startY = w.scrollY || w.pageYOffset;
215      method = original.scroll;
216    } else {
217      scrollable = el;
218      startX = el.scrollLeft;
219      startY = el.scrollTop;
220      method = scrollElement;
221    }
222
223    // scroll looping over a frame
224    step({
225      scrollable: scrollable,
226      method: method,
227      startTime: startTime,
228      startX: startX,
229      startY: startY,
230      x: x,
231      y: y
232    });
233  }
234
235  // ORIGINAL METHODS OVERRIDES
236  // w.scroll and w.scrollTo
237  w.scroll = w.scrollTo = function () {
238    // avoid action when no arguments are passed
239    if (arguments[0] === undefined) {
240      return;
241    }
242
243    // avoid smooth behavior if not required
244    if (shouldBailOut(arguments[0]) === true) {
245      original.scroll.call(
246        w,
247        arguments[0].left !== undefined
248          ? arguments[0].left
249          : typeof arguments[0] !== 'object'
250            ? arguments[0]
251            : w.scrollX || w.pageXOffset,
252        // use top prop, second argument if present or fallback to scrollY
253        arguments[0].top !== undefined
254          ? arguments[0].top
255          : arguments[1] !== undefined
256            ? arguments[1]
257            : w.scrollY || w.pageYOffset
258      );
259
260      return;
261    }
262
263    // LET THE SMOOTHNESS BEGIN!
264    smoothScroll.call(
265      w,
266      d.body,
267      arguments[0].left !== undefined
268        ? ~~arguments[0].left
269        : w.scrollX || w.pageXOffset,
270      arguments[0].top !== undefined
271        ? ~~arguments[0].top
272        : w.scrollY || w.pageYOffset
273    );
274  };
275
276  // w.scrollBy
277  w.scrollBy = function () {
278    // avoid action when no arguments are passed
279    if (arguments[0] === undefined) {
280      return;
281    }
282
283    // avoid smooth behavior if not required
284    if (shouldBailOut(arguments[0])) {
285      original.scrollBy.call(
286        w,
287        arguments[0].left !== undefined
288          ? arguments[0].left
289          : typeof arguments[0] !== 'object' ? arguments[0] : 0,
290        arguments[0].top !== undefined
291          ? arguments[0].top
292          : arguments[1] !== undefined ? arguments[1] : 0
293      );
294
295      return;
296    }
297
298    // LET THE SMOOTHNESS BEGIN!
299    smoothScroll.call(
300      w,
301      d.body,
302      ~~arguments[0].left + (w.scrollX || w.pageXOffset),
303      ~~arguments[0].top + (w.scrollY || w.pageYOffset)
304    );
305  };
306
307  // Element.prototype.scroll and Element.prototype.scrollTo
308  Element.prototype.scroll = Element.prototype.scrollTo = function () {
309    // avoid action when no arguments are passed
310    if (arguments[0] === undefined) {
311      return;
312    }
313
314    // avoid smooth behavior if not required
315    if (shouldBailOut(arguments[0]) === true) {
316      // if one number is passed, throw error to match Firefox implementation
317      if (typeof arguments[0] === 'number' && arguments[1] === undefined) {
318        throw new SyntaxError('Value could not be converted');
319      }
320
321      original.elementScroll.call(
322        this,
323        // use left prop, first number argument or fallback to scrollLeft
324        arguments[0].left !== undefined
325          ? ~~arguments[0].left
326          : typeof arguments[0] !== 'object' ? ~~arguments[0] : this.scrollLeft,
327        // use top prop, second argument or fallback to scrollTop
328        arguments[0].top !== undefined
329          ? ~~arguments[0].top
330          : arguments[1] !== undefined ? ~~arguments[1] : this.scrollTop
331      );
332
333      return;
334    }
335
336    var left = arguments[0].left;
337    var top = arguments[0].top;
338
339    // LET THE SMOOTHNESS BEGIN!
340    smoothScroll.call(
341      this,
342      this,
343      typeof left === 'undefined' ? this.scrollLeft : ~~left,
344      typeof top === 'undefined' ? this.scrollTop : ~~top
345    );
346  };
347
348  // Element.prototype.scrollBy
349  Element.prototype.scrollBy = function () {
350    // avoid action when no arguments are passed
351    if (arguments[0] === undefined) {
352      return;
353    }
354
355    // avoid smooth behavior if not required
356    if (shouldBailOut(arguments[0]) === true) {
357      original.elementScroll.call(
358        this,
359        arguments[0].left !== undefined
360          ? ~~arguments[0].left + this.scrollLeft
361          : ~~arguments[0] + this.scrollLeft,
362        arguments[0].top !== undefined
363          ? ~~arguments[0].top + this.scrollTop
364          : ~~arguments[1] + this.scrollTop
365      );
366
367      return;
368    }
369
370    this.scroll({
371      left: ~~arguments[0].left + this.scrollLeft,
372      top: ~~arguments[0].top + this.scrollTop,
373      behavior: arguments[0].behavior
374    });
375  };
376
377  // Element.prototype.scrollIntoView
378  Element.prototype.scrollIntoView = function () {
379    // avoid smooth behavior if not required
380    if (shouldBailOut(arguments[0]) === true) {
381      original.scrollIntoView.call(
382        this,
383        arguments[0] === undefined ? true : arguments[0]
384      );
385
386      return;
387    }
388
389    // LET THE SMOOTHNESS BEGIN!
390    var scrollableParent = findScrollableParent(this);
391    var parentRects = scrollableParent.getBoundingClientRect();
392    var clientRects = this.getBoundingClientRect();
393
394    if (scrollableParent !== d.body) {
395      // reveal element inside parent
396      smoothScroll.call(
397        this,
398        scrollableParent,
399        scrollableParent.scrollLeft + clientRects.left - parentRects.left,
400        scrollableParent.scrollTop + clientRects.top - parentRects.top
401      );
402
403      // reveal parent in viewport unless is fixed
404      if (w.getComputedStyle(scrollableParent).position !== 'fixed') {
405        w.scrollBy({
406          left: parentRects.left,
407          top: parentRects.top,
408          behavior: 'smooth'
409        });
410      }
411    } else {
412      // reveal element in viewport
413      w.scrollBy({
414        left: clientRects.left,
415        top: clientRects.top,
416        behavior: 'smooth'
417      });
418    }
419  };
420}
421
422function smoothScroll() {
423  smoothScrollPolyfill();
424
425  window.addEventListener('load', function () {
426    setInterval(function () {
427      const jsSmoothScroll = document.getElementsByClassName('js-smooth-scroll');
428      for (let i = 0; i < jsSmoothScroll.length; i++) {
429        jsSmoothScroll[i].addEventListener('click', function(e) {
430          const target = e.currentTarget;
431          const targetId = target.hash;
432          if (!target.classList.contains('js-smooth-scroll')) return;
433          e.preventDefault();
434          document.querySelector(targetId).scrollIntoView({
435            behavior: "smooth",
436            block: "start"
437          });
438        }, false);
439      }
440    }, 100);
441  }, false);
442};

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.