this repo has no description
at main 143 lines 4.0 kB view raw
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}