@@ -10,16 +10,18 @@ const HOLD_MS = 3000
1010const EXIT_STAGGER_MS = 120
1111const EXIT_DURATION_MS = 500
1212
13- interface BlockState {
14- opacity : number
15- transitioning : boolean
13+ const RE_ENTER_OPACITIES = [ 1 , 0.8 , 0.6 , 0.9 ] as const
14+
15+
16+ function setBlockOpacity ( el : HTMLSpanElement | null , opacity : number , animate : boolean ) {
17+ if ( ! el ) return
18+ el . style . transition = animate ? `opacity ${ ENTER_DURATION_MS } ms ease-out` : 'none'
19+ el . style . opacity = String ( opacity )
1620}
1721
1822export function AnimatedColorBlocks ( ) {
1923 const prefersReducedMotion = usePrefersReducedMotion ( )
20- const [ blocks , setBlocks ] = useState < BlockState [ ] > (
21- COLORS . map ( ( ) => ( { opacity : prefersReducedMotion ? 1 : 0 , transitioning : false } ) )
22- )
24+ const blockRefs = useRef < ( HTMLSpanElement | null ) [ ] > ( [ ] )
2325 const timers = useRef < ReturnType < typeof setTimeout > [ ] > ( [ ] )
2426 const mounted = useRef ( true )
2527
@@ -32,17 +34,18 @@ export function AnimatedColorBlocks() {
3234 useEffect ( ( ) => {
3335 mounted . current = true
3436 timers . current = [ ]
37+
3538 if ( prefersReducedMotion ) {
36- setBlocks ( COLORS . map ( ( ) => ( { opacity : 1 , transitioning : false } ) ) )
39+ blockRefs . current . forEach ( ( el ) => setBlockOpacity ( el , 1 , false ) )
3740 return
3841 }
3942
43+ blockRefs . current . forEach ( ( el ) => setBlockOpacity ( el , 0 , false ) )
44+
4045 COLORS . forEach ( ( _ , i ) => {
4146 schedule ( ( ) => {
4247 if ( ! mounted . current ) return
43- setBlocks ( ( prev ) =>
44- prev . map ( ( b , idx ) => ( idx === i ? { opacity : 1 , transitioning : true } : b ) )
45- )
48+ setBlockOpacity ( blockRefs . current [ i ] , 1 , true )
4649 } , i * ENTER_STAGGER_MS )
4750 } )
4851
@@ -65,9 +68,7 @@ export function AnimatedColorBlocks() {
6568 COLORS . forEach ( ( _ , i ) => {
6669 schedule ( ( ) => {
6770 if ( ! mounted . current ) return
68- setBlocks ( ( prev ) =>
69- prev . map ( ( b , idx ) => ( idx === i ? { opacity : 0.15 , transitioning : true } : b ) )
70- )
71+ setBlockOpacity ( blockRefs . current [ i ] , 0.15 , true )
7172 } , i * EXIT_STAGGER_MS )
7273 } )
7374
@@ -77,11 +78,7 @@ export function AnimatedColorBlocks() {
7778 COLORS . forEach ( ( _ , i ) => {
7879 schedule ( ( ) => {
7980 if ( ! mounted . current ) return
80- setBlocks ( ( prev ) =>
81- prev . map ( ( b , idx ) =>
82- idx === i ? { opacity : [ 1 , 0.8 , 0.6 , 0.9 ] [ i ] , transitioning : true } : b
83- )
84- )
81+ setBlockOpacity ( blockRefs . current [ i ] , RE_ENTER_OPACITIES [ i ] , true )
8582 } , i * ENTER_STAGGER_MS )
8683 } )
8784 } , exitTotalMs + 200 )
@@ -96,12 +93,11 @@ export function AnimatedColorBlocks() {
9693 { COLORS . map ( ( color , i ) => (
9794 < span
9895 key = { color }
99- className = 'inline-block h-3 w-3'
100- style = { {
101- backgroundColor : color ,
102- opacity : blocks [ i ] ?. opacity ?? 0 ,
103- transition : `opacity ${ blocks [ i ] ?. transitioning ? ENTER_DURATION_MS : 0 } ms ease-out` ,
96+ ref = { ( el ) => {
97+ blockRefs . current [ i ] = el
10498 } }
99+ className = 'inline-block h-3 w-3'
100+ style = { { backgroundColor : color , opacity : 0 } }
105101 />
106102 ) ) }
107103 </ div >
@@ -110,32 +106,30 @@ export function AnimatedColorBlocks() {
110106
111107export function AnimatedColorBlocksVertical ( ) {
112108 const prefersReducedMotion = usePrefersReducedMotion ( )
113- const [ blocks , setBlocks ] = useState < BlockState [ ] > (
114- COLORS . slice ( 0 , 3 ) . map ( ( ) => ( {
115- opacity : prefersReducedMotion ? 1 : 0 ,
116- transitioning : false ,
117- } ) )
118- )
109+ const blockRefs = useRef < ( HTMLSpanElement | null ) [ ] > ( [ ] )
119110 const timers = useRef < ReturnType < typeof setTimeout > [ ] > ( [ ] )
120111 const mounted = useRef ( true )
121112
113+ const verticalColors = [ COLORS [ 0 ] , COLORS [ 1 ] , COLORS [ 2 ] ] as const
114+
122115 useEffect ( ( ) => {
123116 mounted . current = true
124117 timers . current = [ ]
118+
125119 if ( prefersReducedMotion ) {
126- setBlocks ( COLORS . slice ( 0 , 3 ) . map ( ( ) => ( { opacity : 1 , transitioning : false } ) ) )
120+ blockRefs . current . forEach ( ( el ) => setBlockOpacity ( el , 1 , false ) )
127121 return
128122 }
129123
124+ blockRefs . current . forEach ( ( el ) => setBlockOpacity ( el , 0 , false ) )
125+
130126 const baseDelay = COLORS . length * ENTER_STAGGER_MS + 100
131127
132- COLORS . slice ( 0 , 3 ) . forEach ( ( _ , i ) => {
128+ verticalColors . forEach ( ( _ , i ) => {
133129 const id = setTimeout (
134130 ( ) => {
135131 if ( ! mounted . current ) return
136- setBlocks ( ( prev ) =>
137- prev . map ( ( b , idx ) => ( idx === i ? { opacity : 1 , transitioning : true } : b ) )
138- )
132+ setBlockOpacity ( blockRefs . current [ i ] , 1 , true )
139133 } ,
140134 baseDelay + i * ENTER_STAGGER_MS
141135 )
@@ -149,19 +143,16 @@ export function AnimatedColorBlocksVertical() {
149143 }
150144 } , [ prefersReducedMotion ] )
151145
152- const verticalColors = [ COLORS [ 0 ] , COLORS [ 1 ] , COLORS [ 2 ] ]
153-
154146 return (
155147 < div className = 'flex flex-col gap-0' aria-hidden = 'true' >
156148 { verticalColors . map ( ( color , i ) => (
157149 < span
158150 key = { color }
159- className = 'inline-block h-3 w-3'
160- style = { {
161- backgroundColor : color ,
162- opacity : blocks [ i ] ?. opacity ?? 0 ,
163- transition : `opacity ${ blocks [ i ] ?. transitioning ? ENTER_DURATION_MS : 0 } ms ease-out` ,
151+ ref = { ( el ) => {
152+ blockRefs . current [ i ] = el
164153 } }
154+ className = 'inline-block h-3 w-3'
155+ style = { { backgroundColor : color , opacity : 0 } }
165156 />
166157 ) ) }
167158 </ div >
0 commit comments