diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /shared/components/src/utils/scrollByPolyfill.ts | |
init commit
Diffstat (limited to 'shared/components/src/utils/scrollByPolyfill.ts')
| -rw-r--r-- | shared/components/src/utils/scrollByPolyfill.ts | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/shared/components/src/utils/scrollByPolyfill.ts b/shared/components/src/utils/scrollByPolyfill.ts new file mode 100644 index 0000000..1a73a4f --- /dev/null +++ b/shared/components/src/utils/scrollByPolyfill.ts @@ -0,0 +1,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; +} |
