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