1
+ import React , { useEffect , useRef , useState } from 'react' ;
2
+ import './scroll-timeline.module.css' ;
3
+
4
+ /**
5
+ * ScrollTimeline Example Component
6
+ *
7
+ * This component demonstrates ScrollTimeline API usage and compares:
8
+ * 1. Real browser ScrollTimeline behavior (if supported)
9
+ * 2. Our jsdom-testing-mocks ScrollTimeline implementation
10
+ *
11
+ * The example shows how scroll-driven animations can be created and tested.
12
+ */
13
+ export function ScrollTimelineExample ( ) {
14
+ const containerRef = useRef < HTMLDivElement > ( null ) ;
15
+ const progressBarRef = useRef < HTMLDivElement > ( null ) ;
16
+ const animatedElementRef = useRef < HTMLDivElement > ( null ) ;
17
+ const [ scrollProgress , setScrollProgress ] = useState ( 0 ) ;
18
+ const [ isRealBrowser , setIsRealBrowser ] = useState ( false ) ;
19
+ const [ mockCurrentTime , setMockCurrentTime ] = useState < number | null > ( null ) ;
20
+
21
+ useEffect ( ( ) => {
22
+ // Check if we're in a real browser with native ScrollTimeline support
23
+ setIsRealBrowser ( typeof window !== 'undefined' && 'ScrollTimeline' in window ) ;
24
+
25
+ if ( ! containerRef . current || ! progressBarRef . current || ! animatedElementRef . current ) {
26
+ return ;
27
+ }
28
+
29
+ const container = containerRef . current ;
30
+ const progressBar = progressBarRef . current ;
31
+ const animatedElement = animatedElementRef . current ;
32
+
33
+ try {
34
+ // Create ScrollTimeline (works with both real browser and our mock)
35
+ const scrollTimeline = new ScrollTimeline ( {
36
+ source : container ,
37
+ axis : 'block'
38
+ } ) ;
39
+
40
+ // Create animation using ScrollTimeline
41
+ const animation = new Animation (
42
+ new KeyframeEffect (
43
+ animatedElement ,
44
+ [
45
+ { transform : 'translateX(0px)' , backgroundColor : 'red' } ,
46
+ { transform : 'translateX(200px)' , backgroundColor : 'blue' }
47
+ ] ,
48
+ { duration : 1000 }
49
+ ) ,
50
+ scrollTimeline
51
+ ) ;
52
+
53
+ animation . play ( ) ;
54
+
55
+ // Monitor scroll progress
56
+ const updateProgress = ( ) => {
57
+ const scrollTop = container . scrollTop ;
58
+ const scrollHeight = container . scrollHeight - container . clientHeight ;
59
+ const progress = scrollHeight > 0 ? ( scrollTop / scrollHeight ) * 100 : 0 ;
60
+
61
+ setScrollProgress ( progress ) ;
62
+ setMockCurrentTime ( scrollTimeline . currentTime ) ;
63
+
64
+ // Update progress bar
65
+ progressBar . style . width = `${ progress } %` ;
66
+ } ;
67
+
68
+ container . addEventListener ( 'scroll' , updateProgress ) ;
69
+ updateProgress ( ) ; // Initial call
70
+
71
+ return ( ) => {
72
+ container . removeEventListener ( 'scroll' , updateProgress ) ;
73
+ animation . cancel ( ) ;
74
+ } ;
75
+ } catch ( error ) {
76
+ console . error ( 'ScrollTimeline not available:' , error ) ;
77
+ }
78
+ } , [ ] ) ;
79
+
80
+ return (
81
+ < div className = "scroll-timeline-example" >
82
+ < h2 > ScrollTimeline API Example</ h2 >
83
+
84
+ < div className = "info-panel" >
85
+ < p > < strong > Environment:</ strong > { isRealBrowser ? 'Real Browser' : 'jsdom with Mock' } </ p >
86
+ < p > < strong > Scroll Progress:</ strong > { scrollProgress . toFixed ( 1 ) } %</ p >
87
+ < p > < strong > Timeline.currentTime:</ strong > { mockCurrentTime ?. toFixed ( 1 ) || 'null' } </ p >
88
+ </ div >
89
+
90
+ < div className = "demo-container" >
91
+ < div
92
+ ref = { containerRef }
93
+ className = "scrollable-container"
94
+ >
95
+ < div className = "scroll-content" >
96
+ < h3 > Scroll down to see the animation</ h3 >
97
+ < p > This content is much taller than the container to enable scrolling.</ p >
98
+ < p > The red square will move and change color as you scroll...</ p >
99
+
100
+ { /* Fill content to make scrolling possible */ }
101
+ { Array . from ( { length : 20 } , ( _ , i ) => (
102
+ < div key = { i } className = "content-section" >
103
+ < p > Content section { i + 1 } </ p >
104
+ < p > Keep scrolling to see the ScrollTimeline animation in action.</ p >
105
+ </ div >
106
+ ) ) }
107
+
108
+ < p > 🎉 You've reached the end! The animation should be complete.</ p >
109
+ </ div >
110
+ </ div >
111
+
112
+ < div className = "animation-display" >
113
+ < div className = "progress-container" >
114
+ < div className = "progress-label" > Scroll Progress:</ div >
115
+ < div className = "progress-track" >
116
+ < div ref = { progressBarRef } className = "progress-bar" > </ div >
117
+ </ div >
118
+ </ div >
119
+
120
+ < div className = "animated-element-container" >
121
+ < div ref = { animatedElementRef } className = "animated-element" >
122
+ 📦
123
+ </ div >
124
+ </ div >
125
+ </ div >
126
+ </ div >
127
+
128
+ < div className = "explanation" >
129
+ < h3 > How it works:</ h3 >
130
+ < ul >
131
+ < li > < strong > ScrollTimeline:</ strong > Creates a timeline driven by scroll position</ li >
132
+ < li > < strong > Animation:</ strong > Uses the ScrollTimeline instead of time-based duration</ li >
133
+ < li > < strong > Progress:</ strong > Animation progress matches scroll progress (0-100%)</ li >
134
+ < li > < strong > Mock vs Real:</ strong > Our mock provides the same API as the real browser implementation</ li >
135
+ </ ul >
136
+
137
+ < h3 > Testing Benefits:</ h3 >
138
+ < ul >
139
+ < li > Test scroll-driven animations in jsdom without browser dependency</ li >
140
+ < li > Predictable behavior for automated testing</ li >
141
+ < li > Same API surface as real browser ScrollTimeline</ li >
142
+ < li > Easy to control scroll state programmatically in tests</ li >
143
+ </ ul >
144
+ </ div >
145
+ </ div >
146
+ ) ;
147
+ }
0 commit comments