I have three sequential images that are being appended to the DOM using setInterval
within useEffect
. The end product looks like a interactive image and it works great, minus the fact that there's a very, very subtle jitter when each image is mounted. I wrote this very quickly and have not refactored, so forgive the verbosity.
import styles from './HeroImageSection.module.css'
import Image from 'next/image'
import React, { useState, useEffect, useRef } from 'react';
import Link from 'next/link'
import slideOne from './../../../public/assets/hero_images/1_nonumber.png'
import slideTwo from './../../../public/assets/hero_images/2_nonumber.png'
import slideThree from './../../../public/assets/hero_images/3_nonumber.png'
import Logo65Font from '../../global/ui/Logo65Font.js'
export default function HeroImageSection() {
const [isShowing, setIsShowing] = useState({
slideOne: true,
slideTwo: '',
slideThree: ''
})
useEffect(() =>{
let timer = setInterval(() => {
if (isShowing['slideOne'] === true){
setIsShowing(prevState => ({...prevState, ['slideOne']: false}))
setIsShowing(prevState => ({...prevState, ['slideTwo']: true}))
setIsShowing(prevState => ({...prevState, ['slideThree']: false}))
} else if (isShowing['slideThree'] === true && isShowing['slideOne'] === false){
setIsShowing(prevState => ({...prevState, ['slideOne']: true}))
setIsShowing(prevState => ({...prevState, ['slideTwo']: false}))
setIsShowing(prevState => ({...prevState, ['slideThree']: false}))
} else if (isShowing['slideOne'] === true && isShowing['slideTwo'] === false){
setIsShowing(prevState => ({...prevState, ['slideOne']: false}))
setIsShowing(prevState => ({...prevState, ['slideTwo']: true}))
setIsShowing(prevState => ({...prevState, ['slideThree']: false}))
} else if (isShowing['slideTwo'] === true && isShowing['slideThree'] === false){
setIsShowing(prevState => ({...prevState, ['slideOne']: false}))
setIsShowing(prevState => ({...prevState, ['slideTwo']: false}))
setIsShowing(prevState => ({...prevState, ['slideThree']: true}))
}
}, 1000)
return () => clearInterval(timer);
}, [isShowing])
return(
<section id={styles.heroImageSectionContainer}>
<div id={styles.centerImageTextContainer}>
<div id={styles.mobileLogo}>
<Logo65Font />
</div>
<div id={styles.heroTextContainer}>
<h1>Untouched pallets ready to be delivered to your driveway<span id={styles.periodColor}>.</span></h1>
<p><Link href={`/register`}><span id={styles.signUpUnderline}>Sign up</span></Link> to see shipping rates as you shop</p>
</div>
<div id={styles.heroImageContainer}>
<div className={isShowing['slideOne'] === true ? styles.centerImage : styles.displayNone}>
<Image alt="The flow of goods from retailer origin to your house." src={slideOne} priority={true} />
</div>
<div className={isShowing['slideTwo'] === true ? styles.centerImage : styles.displayNone}>
<Image alt="The flow of goods from retailer origin to your house." src={slideTwo} priority={true} />
</div>
<div className={isShowing['slideThree'] === true ? styles.centerImage : styles.displayNone}>
<Image alt="The flow of goods from retailer origin to your house." src={slideThree} priority={true} />
</div>
</div>
</div>
</section>
)
}
I am using boolean values per image to track whether or not they should be displayed depending on the state of the other two. Depending on truthiness, I either use a class that has display: none
or I add the necessary styling class. Obviously, the jitter is because of the removal of the classes but I am not sure how I can smooth that out, if at all?
Let's say we do it the "react way" and shy away from manipulating classes like so:
{isShowing['slideOne'] === true ?
<div className={styles.centerImage}>
<Image alt="The flow of goods from retailer origin to your house." src={slideOne} priority={true} />
</div>
: ''}
{isShowing['slideTwo'] === true ?
<div className={styles.centerImage}>
<Image alt="The flow of goods from retailer origin to your house." src={slideTwo} priority={true} />
</div>
: ''}
{isShowing['slideThree'] === true ?
<div className={styles.centerImage}>
<Image alt="The flow of goods from retailer origin to your house." src={slideThree} priority={true} />
</div>
: ''}
The end result is the same, except the first run through of the transition causes a flicker, so it actually adds another bug to the process.
I am using a flexbox layout and here is what the centerImage
class looks like:
#centerImage{
display: flex;
width: 70%;
height: 100%;
justify-content: center;
}
Hopefully you guys have some suggestions!
EDIT: The images are all the exact same size too.
CodePudding user response:
You use condition rendering to hide/show images. When the image is mounted for the first time it's loaded, and that causes the jitter.
Mount all images at the same time, and use a class to hide/show them, and a transition to smoothen the change.
const { useState, useEffect } = React;
const images = [
'https://picsum.photos/id/236/400/600',
'https://picsum.photos/id/237/400/600',
'https://picsum.photos/id/238/400/600'
];
function HeroImageSection() {
const [currentImage, setCurrentImage] = useState(0)
useEffect(() =>{
const timer = setInterval(() => {
setCurrentImage(prev => (prev 1) % images.length);
}, 1000);
return () => clearInterval(timer);
}, [])
return(
<div>
{images.map((url, idx) =>
<img key={url} src={url} className={`image ${idx === currentImage ? 'show-image' : ''}`} />
)}
</div>
)
}
ReactDOM
.createRoot(root)
.render(<HeroImageSection />);
.container {
position: relative;
height: 600px;
width: 400px;
}
.image {
position: absolute;
top: 0;
left: 0;
opacity: 0;
transition: opacity 0.5s;
}
.show-image {
opacity: 1;
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>
Note: if you have large images, loading might still take more time. Try to compress the images, and also consider starting the animation only after all images loaded (see examples here).