I am trying to make a react slide show component with the following method:
- The component gets an array of slides (react components) as a prop, it shows each slide at a time starting from slide number 1 (index 0).
- The currently displayed slide (aka "current slide") is shown while the other slides are not displayed.
- When navigating (starting the slide animation), the destination slide will be added to the wrapper. if it is before the current slide (if the destination slide has a smaller index in the slides array, aka click on next), it will be added
with animation of
leftfrom -100% to 0(slide-in from left),
and if it's after (bigger destination index, aka clicked on previous), it will have animationfrom 100% to 0(slide-in from right);
while for the current slide, an animation class ofleft 0 to 100%, orleft 0 to -100%will be added correspondingly (slide-out to right or left). - When the animation ends, the current slide will be changed to the slided-in slide (the destination slide). and the previous current slide will get removed.
Slides component (glitching):
import React, { useCallback, useState } from 'react';
import styles from './Slides.module.css';
function Slides({ slides, onBrowse, onBrowseEnd, className, ...rest }) {
const [currentSlide, setCurrentSlide] = useState(1); //slide number
const [animSlide, setAnimSlide] = useState(null); //destination slide number
const browse = useCallback((slide) => {
if (slide <= slides.length) {
setAnimSlide(slide);
console.log(slide);
}
onBrowse && onBrowse(slide);
});
console.log('rerender');
const animationEndCallback = useCallback(() => {
setCurrentSlide(animSlide);
setAnimSlide(null);
}, [animSlide]);
return (
<div className={styles.wrapper + ' ' + className} {...rest}>
<div className={styles.slides}>
{animSlide &&
React.cloneElement(slides[animSlide - 1], {
className: `
${styles.absolute}
${styles.slide}
${slides[animSlide - 1]?.props?.className}
${
animSlide < currentSlide
? styles.slide_in_from_left
: styles.slide_in_from_right
}
`,
})}
{React.cloneElement(slides[currentSlide - 1], {
className: `
${styles.absolute}
${styles.slide}
${slides[currentSlide - 1]?.props?.className}
${
animSlide &&
(animSlide < currentSlide
? styles.slide_right
: styles.slide_left)
}
`,
onAnimationEnd: animationEndCallback,
})}
</div>
{
}
<div className={styles.navcon}>
<button
onClick={() => {
browse(currentSlide - 1);
}}
disabled={!(currentSlide > 1)}
>
Previuos
</button>
<button
onClick={() => {
browse(currentSlide + 1);
}}
disabled={
!(animSlide
? animSlide < slides.length
: currentSlide < slides.length)
}
>
Next
</button>
</div>
</div>
);
}
export default Slides;
I noticed that if I put the destination slide after the current slide in the wrapper component it won't have any problem, but if I put it before, it will flicker.
I wanted to understand, why?
Not flickering (animation slide is after current slide):
demo: https://stackblitz.com/edit/vitejs-vite-wnedwd?file=src%2FSlides.jsx

flickering (animation slide is before current slide): demo: https://stackblitz.com/edit/vitejs-vite-hy6rsp?file=src%2FSlides.jsx

Difference
When the animation is running (phase A), both elements are displayed in the DOM, but when it is not(phase N), only one is displayed.
If we focus on "phase A", especially the second returned element in the JSX.
In the first example it is the one that will be displayed alone in "phase N", however, in the second example, it is not, it is the other one instead.
Impact
If we wrap the code inside your callback function with a
setTimeoutso we can see what's displayed on the screen after the animation ends and when both elements are present in DOM (phase A)We notice that the second element returned is always the one that is shown on the screen after the animation ends.
Answer
This explains what's happening.
There is a moment when the animation is over but the component didn't rerender yet and both are present in DOM, it is in fact, that moment when the code inside the callback function is running, therefore, at that time, we see the second returned element displayed on the screen before the component rerenders and only
currentSlide-1cloned element is present. I am pretty sure this is the reason behind the flickering and why it isnot happening in the first example.Why?
Now since I am a CSS beginner, I tried to simplify the code by getting rid of the animation and just including both elements to understand why only the second one is shown when both are present
If you try the above code you will see that till now only the second one is shown so this has nothing to do with the animation, however, when your try to add/remove
${styles.absolute}for both you notice that the one that takesclassName={styles.absolute}ieposition: absoluteis the one displayed and when both takes it, the last that receive it in run time is the one that is in the front and this also explains whyz-indexin Hadi KAR answer fix that.When I get rid of
${styles.absolute}in both elements, now when no one takesposition: absoluteI expected to see them both but the result is confusing, this time we see only the first one, makes me look into the CSS class of their wrapperdivi.estyles.slides, there, you have specifiedflex: 1along withoverflow: hidden.if we simplify it to this:
This makes children displayed under each other following the input order, each filling the screen however if we add
overflow: hiddento the class, the second one is hidden since it is overflowing.Finally, I want to thank you for posting this question, it makes me learn new CSS stuff