@@ -17,7 +17,7 @@ const overflowStyles = (direction: ScrollDirection): React.CSSProperties => ({
1717 scrollTimelineAxis : direction === 'horizontal' ? 'x' : 'y' ,
1818} )
1919
20- const fadeStyles = ( direction : FadeDirection , id : string , horizontal : boolean , color : string ) : React . CSSProperties => ( {
20+ const fadeStyles = ( direction : FadeDirection , horizontal : boolean , color : string ) : React . CSSProperties => ( {
2121 display : 'flex' ,
2222 position : 'absolute' ,
2323 outline : 'none' ,
@@ -27,10 +27,6 @@ const fadeStyles = (direction: FadeDirection, id: string, horizontal: boolean, c
2727 height : horizontal ? '100%' : 20 ,
2828 width : horizontal ? 20 : '100%' ,
2929 background : `linear-gradient(to ${ direction } , rgba(255, 255, 255, 0), ${ color } )` ,
30- animationTimeline : '--indicate-scroll-element' ,
31- animationTimingFunction : 'linear' ,
32- animationFillMode : 'forwards' ,
33- animationName : `indicate-${ direction } -${ id } ` ,
3430 left : direction === 'left' ? 0 : 'auto' ,
3531 right : direction === 'right' ? 0 : 'auto' ,
3632 top : direction === 'top' ? 0 : 'auto' ,
@@ -39,31 +35,47 @@ const fadeStyles = (direction: FadeDirection, id: string, horizontal: boolean, c
3935 visibility : direction === 'left' || direction === 'top' ? 'hidden' : 'visible' ,
4036} )
4137
42- function updateScrollVariables ( element : HTMLDivElement , id : string , style : HTMLStyleElement ) {
38+ function setScrollTimelineAnimation (
39+ element : HTMLDivElement ,
40+ fade : HTMLButtonElement ,
41+ direction : FadeDirection ,
42+ scrollDirection : ScrollDirection ,
43+ ) {
4344 const scrollHeight = element . scrollHeight - element . clientHeight
4445 const scrollWidth = element . scrollWidth - element . clientWidth
45- const horizontalPercentage = 20 / ( scrollWidth / element . clientWidth )
46- const verticalPercentage = 20 / ( scrollHeight / element . clientHeight )
46+ const horizontalPercentage = scrollWidth / element . clientWidth / 100
47+ const verticalPercentage = scrollHeight / element . clientHeight / 100
4748
48- style . textContent = keyframes ( id , scrollWidth < 1 ? 20 : horizontalPercentage , scrollHeight < 1 ? 20 : verticalPercentage )
49- }
49+ const isHorizontal = direction === 'left' || direction === 'right'
5050
51- const keyframes = ( id : string , horizontalOffsetPercentage : number , verticalOffsetPercentage : number ) => `@keyframes indicate-top-${ id } {
52- ${ verticalOffsetPercentage } % { opacity: 1; visibility: visible; }
53- 100% { opacity: 1; visibility: visible; }
54- }
55- @keyframes indicate-right-${ id } {
56- ${ 100 - horizontalOffsetPercentage } % { opacity: 1; visibility: visible; }
57- 100% { opacity: 0; visibility: hidden; }
58- }
59- @keyframes indicate-bottom-${ id } {
60- ${ 100 - verticalOffsetPercentage } % { opacity: 1; visibility: visible; }
61- 100% { opacity: 0; visibility: hidden; }
51+ if ( isHorizontal && ( scrollWidth < 1 || horizontalPercentage === Number . POSITIVE_INFINITY ) ) {
52+ return
53+ }
54+
55+ if ( ! isHorizontal && ( scrollHeight < 1 || verticalPercentage === Number . POSITIVE_INFINITY ) ) {
56+ return
57+ }
58+
59+ const scrollTimeline = new ScrollTimeline ( {
60+ source : element ,
61+ axis : scrollDirection === 'horizontal' ? 'inline' : 'block' ,
62+ } )
63+
64+ const start = direction === 'left' || direction === 'top'
65+ fade . animate (
66+ {
67+ opacity : start ? [ 0 , 0 , 1 ] : [ 1 , 1 , 0 ] ,
68+ visibility : start ? [ 'hidden' , 'visible' , 'visible' ] : [ 'visible' , 'visible' , 'hidden' ] ,
69+ offset : start
70+ ? [ 0 , direction === 'top' ? verticalPercentage : horizontalPercentage , 1 ]
71+ : [ 0 , direction === 'bottom' ? 1 - verticalPercentage : 1 - horizontalPercentage , 1 ] ,
72+ } ,
73+ {
74+ timeline : scrollTimeline ,
75+ fill : 'both' ,
76+ } ,
77+ )
6278}
63- @keyframes indicate-left-${ id } {
64- ${ horizontalOffsetPercentage } % { opacity: 1; visibility: visible; }
65- 100% { opacity: 1; visibility: visible; }
66- }`
6779
6880const scrollByDirection = {
6981 top : ( container : HTMLDivElement ) => ( {
@@ -139,26 +151,37 @@ function Fallback<T extends keyof React.JSX.IntrinsicElements = 'div'>({
139151
140152function Fade ( {
141153 direction,
142- id ,
154+ scrollDirection ,
143155 style,
144156 color,
145157 arrow,
158+ ref,
159+ element,
146160} : {
147161 direction : FadeDirection
148- id : string
162+ scrollDirection : ScrollDirection
149163 style ?: React . CSSProperties
150164 color : string
151165 arrow : ArrowProps | boolean
166+ ref : React . RefObject < HTMLButtonElement | null >
167+ element : HTMLDivElement | null
152168} ) {
153169 const horizontal = direction === 'left' || direction === 'right'
154170 const arrowConfiguration = typeof arrow === 'object' ? arrow : defaultArrowProps
155171
172+ useEffect ( ( ) => {
173+ if ( element && ref . current ) {
174+ setScrollTimelineAnimation ( element , ref . current , direction , scrollDirection )
175+ }
176+ } , [ ref , direction , scrollDirection , element ] )
177+
156178 return (
157179 < button
180+ ref = { ref }
158181 aria-label = { `Scroll to ${ direction } ` }
159182 type = "button"
160183 style = { {
161- ...fadeStyles ( direction , id , horizontal , color ) ,
184+ ...fadeStyles ( direction , horizontal , color ) ,
162185 ...style ,
163186 alignItems : getArrowPosition ( arrowConfiguration ) ,
164187 justifyContent : getArrowPosition ( arrowConfiguration ) ,
@@ -190,9 +213,11 @@ export function Scroll<T extends keyof React.JSX.IntrinsicElements = 'div'>({
190213 ...props
191214} : Props < T > ) {
192215 const [ hasOverflow , setHasOverflow ] = useState ( false )
193- const [ id ] = useState ( ( ) => Math . random ( ) . toString ( 36 ) . substring ( 2 , 10 ) )
194216 const scrollRef = useRef < HTMLDivElement > ( null )
195- const styleRef = useRef < HTMLStyleElement > ( null )
217+ const fadeTopRef = useRef < HTMLButtonElement > ( null )
218+ const fadeRightRef = useRef < HTMLButtonElement > ( null )
219+ const fadeBottomRef = useRef < HTMLButtonElement > ( null )
220+ const fadeLeftRef = useRef < HTMLButtonElement > ( null )
196221
197222 useEffect ( ( ) => {
198223 const element = scrollRef . current
@@ -204,22 +229,12 @@ export function Scroll<T extends keyof React.JSX.IntrinsicElements = 'div'>({
204229
205230 checkOverflow ( element , direction , hasOverflow , setHasOverflow )
206231
207- if ( styleRef . current ) {
208- updateScrollVariables ( element , id , styleRef . current )
209- }
210-
211232 const resizeObserver = new ResizeObserver ( ( ) => {
212233 checkOverflow ( element , direction , hasOverflow , setHasOverflow )
213- if ( styleRef . current ) {
214- // updateScrollVariables(element, styleRef.current)
215- }
216234 } )
217235
218236 const observer = new MutationObserver ( ( ) => {
219237 checkOverflow ( element , direction , hasOverflow , setHasOverflow )
220- if ( styleRef . current ) {
221- // updateScrollVariables(element, styleRef.current)
222- }
223238 } )
224239
225240 // Observe if more children are rendered.
@@ -237,7 +252,7 @@ export function Scroll<T extends keyof React.JSX.IntrinsicElements = 'div'>({
237252 resizeObserver . unobserve ( element )
238253 }
239254 }
240- } , [ hasOverflow , direction , id ] )
255+ } , [ hasOverflow , direction ] )
241256
242257 if ( ! supportsScrollTimeline ) {
243258 return (
@@ -260,11 +275,50 @@ export function Scroll<T extends keyof React.JSX.IntrinsicElements = 'div'>({
260275 { children }
261276 { hasOverflow && (
262277 < >
263- < style ref = { styleRef } > { keyframes ( id , 20 , 20 ) } </ style >
264- { direction === 'vertical' && < Fade style = { indicatorStyle } color = { color } direction = "top" id = { id } arrow = { arrow } /> }
265- { direction === 'horizontal' && < Fade style = { indicatorStyle } color = { color } direction = "right" id = { id } arrow = { arrow } /> }
266- { direction === 'vertical' && < Fade style = { indicatorStyle } color = { color } direction = "bottom" id = { id } arrow = { arrow } /> }
267- { direction === 'horizontal' && < Fade style = { indicatorStyle } color = { color } direction = "left" id = { id } arrow = { arrow } /> }
278+ { direction === 'vertical' && (
279+ < Fade
280+ ref = { fadeTopRef }
281+ element = { scrollRef . current }
282+ style = { indicatorStyle }
283+ color = { color }
284+ direction = "top"
285+ scrollDirection = { direction }
286+ arrow = { arrow }
287+ />
288+ ) }
289+ { direction === 'horizontal' && (
290+ < Fade
291+ ref = { fadeRightRef }
292+ element = { scrollRef . current }
293+ style = { indicatorStyle }
294+ color = { color }
295+ direction = "right"
296+ scrollDirection = { direction }
297+ arrow = { arrow }
298+ />
299+ ) }
300+ { direction === 'vertical' && (
301+ < Fade
302+ ref = { fadeBottomRef }
303+ element = { scrollRef . current }
304+ style = { indicatorStyle }
305+ color = { color }
306+ direction = "bottom"
307+ scrollDirection = { direction }
308+ arrow = { arrow }
309+ />
310+ ) }
311+ { direction === 'horizontal' && (
312+ < Fade
313+ ref = { fadeLeftRef }
314+ element = { scrollRef . current }
315+ style = { indicatorStyle }
316+ color = { color }
317+ direction = "left"
318+ scrollDirection = { direction }
319+ arrow = { arrow }
320+ />
321+ ) }
268322 </ >
269323 ) }
270324 </ div >
0 commit comments