this repo has no description
1// COPIED FROM
2// https://github.pie.apple.com/amp-ui/ember-ui-media-shelf/blob/580ff07a546771bce8b3d85494c6268860e97215/addon/-private/scroll-by-polyfill.js
3
4const SCROLL_TIME = 468;
5const Element =
6 typeof window !== 'undefined' ? window.HTMLElement || window.Element : null;
7
8let originalScrollBy;
9
10/**
11 * returns result of applying ease math function to a number
12 * @method ease
13 * @param {Number} k
14 * @returns {Number}
15 */
16function ease(k: number): number {
17 return 0.5 * (1 - Math.cos(Math.PI * k));
18}
19
20// define timing method
21const now: () => number =
22 typeof window !== 'undefined' && window?.performance?.now
23 ? window.performance.now.bind(window.performance)
24 : Date.now;
25
26/**
27 * changes scroll position inside an element
28 * @method scrollElement
29 * @param {Number} x
30 * @returns {undefined}
31 */
32function scrollElement(x: number): void {
33 this.scrollLeft = x;
34}
35
36/**
37 * self invoked function that, given a context, steps through scrolling
38 * @method step
39 * @param {Object} context
40 * @returns {undefined}
41 */
42type Context = {
43 startTime: number;
44 startX: number;
45 x: number;
46 method: (x: number) => void;
47 scrollable: HTMLElement;
48};
49function step(context: Context): void {
50 const time = now();
51 let elapsed = (time - context.startTime) / SCROLL_TIME;
52
53 // avoid elapsed times higher than one
54 elapsed = Math.min(1, elapsed);
55
56 // apply easing to elapsed time
57 const value = ease(elapsed);
58
59 const currentX = context.startX + (context.x - context.startX) * value;
60
61 context.method.call(context.scrollable, currentX);
62
63 // scroll more if we have not reached our destination
64 if (currentX !== context.x) {
65 window.requestAnimationFrame(step.bind(window, context));
66 }
67}
68
69/**
70 * scrolls window or element with a smooth behavior
71 * @method smoothScroll
72 * @param {Object|Node} el
73 * @param {Number} x
74 * @returns {undefined}
75 */
76function smoothScroll(el: HTMLElement, x: number): void {
77 const startTime = now();
78 // define scroll context
79 const startX = el.scrollLeft;
80 const method = scrollElement;
81
82 // scroll looping over a frame
83 step({
84 scrollable: el,
85 method,
86 startTime,
87 startX,
88 x,
89 });
90}
91
92let polyfillHasRun = false;
93/**
94 * ripped partially from https://github.com/iamdustan/smoothscroll/blob/master/src/smoothscroll.js
95 * Only polyfill horizontal scroll space to avoid unexpected behaviour in parent apps
96 *
97 * @method scrollByPolyfill
98 */
99export default function scrollByPolyfill(): void {
100 // return if scroll behavior is supported
101 if ('scrollBehavior' in document.documentElement.style || polyfillHasRun) {
102 return;
103 }
104
105 // if prefers-reduce-motion && need polyfill, navigate shelf immediately without easing
106 const motionMediaQuery = window.matchMedia(
107 '(prefers-reduced-motion: reduce)',
108 );
109 function addScrollByToProto() {
110 if (motionMediaQuery.matches) {
111 if (originalScrollBy) {
112 Element.prototype.scrollBy = originalScrollBy;
113 }
114 return;
115 }
116
117 function scrollByPoly(options: ScrollToOptions): void;
118 function scrollByPoly(x: number, _y: number): void;
119 // eslint-disable-next-line @typescript-eslint/no-unused-vars
120 function scrollByPoly(
121 paramOne: number | ScrollToOptions,
122 _paramTwo?: number,
123 ): void {
124 let xValue = 0;
125 if (typeof paramOne === 'number') {
126 xValue = paramOne;
127 } else if (typeof paramOne === 'object') {
128 xValue = paramOne.left || 0;
129 }
130
131 const moveByX = this.scrollLeft + xValue;
132 smoothScroll(this, moveByX);
133 }
134
135 originalScrollBy = Element.prototype.scrollBy;
136 Element.prototype.scrollBy = scrollByPoly;
137 }
138
139 motionMediaQuery.addListener(addScrollByToProto);
140
141 addScrollByToProto();
142 polyfillHasRun = true;
143}