I am rotating the items in an array of images inside an interval of 5 seconds. Then I have a css animation with styled components that eases in the gallery images with a fade that occurs at the same time interval.
const fadeIn = keyframes`
5%, 95% { opacity: 1 }
100% { opacity: 0 }
`
export const Gallery = styled.div<{ lapse: number }>`
position: relative;
margin: 0 auto;
max-width: 1064px;
opacity: 0;
animation: ${fadeIn} ease-in-out ${({ lapse }) => `${lapse}s`} infinite;
`
The problem is that when I change the state
even thou at first it seems in sync, eventually the setState
takes a bit longer
import React, { useState, useEffect } from 'react'
const [images, setImages] = useState<string[]>([])
// Time in seconds for each image swap, fading in between
const lapse = 5
...
useEffect(() => {
// Clone the images array
const imgs = [...images]
// Time interval same as css animation to fade in
const interval = setInterval(() => {
// Take the first element and put it at the end
imgs.push(...imgs.splice(0, 1))
// Update the state, this seems to desync as time passes
setImages(imgs)
}, lapse * 1000)
return () => clearInterval(interval)
}, [images])
return (
<Gallery lapse={lapse}>
<Portal>
<img src={imgs/${images[0]}`}
</Portal>
<Thumbnails>
<Thumbwrapper>
<img src={imgs/${images[1]}`}
</Thumbwrapper>
<Thumbwrapper>
<img src={imgs/${images[2]}`}
</Thumbwrapper>
</Thumbnails>
</Gallery>
)
Is there a way I can make sure the swapping happends smoothly?
I came to the conclusion that having two 'clocks' to keep the time, one being react setState and the other css animation, was at the core of the problem.
So I am now keeping a single interval with a setTimeout to make sure it goes in order then substract the time taken on the timeouts from the interval and toggle the class for css
export const Gallery = styled.div<{ fadeIn: boolean }>`
position: relative;
margin: 0 auto;
max-width: 1064px;
transition: opacity 0.3s;
opacity: ${({ fadeIn }) => (fadeIn ? 1 : 0)};
`
import React, { useState, useEffect } from 'react'
const [images, setImages] = useState<string[]>([])
const [fadeIn, setFadeIn] = useState<boolean>(true)
useEffect(() => {
let interval: ReturnType<typeof setInterval>
if (images.length) {
interval = setInterval(() => {
setFadeIn(false)
setTimeout(() => {
setImages(images => images.slice(1).concat(images[0]) )
setFadeIn(true)
}, 400)
}, (lapse - 0.4) * 1000)
}
return () => clearInterval(interval)
}, [images])
const getImage = (i: number) => <img src={imgs/${images[i]}`} />
<Gallery fadeIn={fadeIn}>
<Portal>{getImage(0)}</Portal>
<Thumbnails>
<Thumbwrapper>{getImage(1)}</Thumbwrapper>
<Thumbwrapper>{getImage(2)}</Thumbwrapper>
</Thumbnails>
</Gallery>
I did however used @Nice Books better setState
aproach