The problem
1. There is a difference between transition and animation in CSS
concepts, between them, there is the definition of "pause" (so to
speak) is only available in animations
It is noticed that it
is possible to imitate this functionality (pause an transition)
using javascript, but if we depend only on CSS, this is impossible.
Reference: How do I pause CSS transition?
2. You are using a CSS property that is meant for animations, and there no transitions, which is: animation-play-state
Importante note
It is important to note that it is possible to remove a css transition, simply removing the transition property, or overwriting it, example: transition: none !important, but when removing a transition, it is not possible to resume it and the "animation" is lost in this process.
The solution
1. Create a function capable of restarting an animation, note that this is importante because at the end of the first animation (onAnimationEnd) it is necessary to restart the count of x seconds for the progress bar, otherwise, it would only work once.
Note: The key to restarting a CSS animation is to set the animation-name of an animation to 'none' and then setting it back to the original animation
Example:
const startAnimation = () => {
if (!progressRef.current) return;
const progress = progressRef.current;
progress.style.animationName = "none";
progress.style.width = "100%";
setTimeout(() => {
progress.style.animationName = "width";
progress.style.animationDuration = `${slideShowDelay}s`;
}, 50);
}
2. Call this function on every slide change, whether it's forward or previous, example:
First option:
const prevButtonHandler = () => {
startAnimation(); // <<- HERE
setCurrentSlide((prev) => {
const result = prev - 1;
if (result < 0) {
return images.length - 1;
}
return result;
});
};
const nextButtonHandler = useCallback(() => {
startAnimation(); // <<- HERE
setCurrentSlide((prev) => {
const result = prev + 1;
if (result >= images.length) {
return 0;
}
return result;
});
}, [images.length]);
OR
Second option:
Or better yet, we can put the startAnimation
function inside useEffect
which will capture every slide change, so no matter the slide is coming back or forward, the application will work normally.
Example:
useEffect(() => {
startAnimation();
}, [currentSlide, slideShowDelay]);
3. Create the animation frame in CSS:
@-webkit-keyframes width {
from {
width: 100%;
}
to {
width: 0%;
}
}
@keyframes width {
from {
width: 100%;
}
to {
width: 0%;
}
}
In the example above we have specified when the style will change by using the keywords "from" and "to" (which represents 0% (start) and 100% (complete)).
4. Change using onTransitionEnd to onAnimationEnd, example:
<div
onAnimationEnd={animationEndHandler}
ref={progressRef}
role={"progressbar"}
className={classes}
/>
The code
Typescript
import { Fragment, useEffect, useState, useCallback, useRef } from "react";
import "./Slider.css";
type Props = {
images: string[];
slideShowDelay?: number;
};
export default function Slider({
images,
slideShowDelay = 5
}: Props): JSX.Element {
const [currentSlide, setCurrentSlide] = useState<number>(0);
const progressRef = useRef<HTMLDivElement | null>(null);
const [animationState, setAnimationState] = useState("animate");
const startAnimation = () => {
if (!progressRef.current) return;
const progress = progressRef.current;
progress.style.animationName = "none";
progress.style.width = "100%";
setTimeout(() => {
progress.style.animationName = "width";
progress.style.animationDuration = `${slideShowDelay}s`;
}, 50);
}
const prevButtonHandler = () => {
setCurrentSlide((prev) => {
const result = prev - 1;
if (result < 0) {
return images.length - 1;
}
return result;
});
};
const nextButtonHandler = useCallback(() => {
setCurrentSlide((prev) => {
const result = prev + 1;
if (result >= images.length) {
return 0;
}
return result;
});
}, [images.length]);
useEffect(() => {
startAnimation();
}, [currentSlide, slideShowDelay]);
const slides = images.map((image, i) => {
const styles = {
transform: `translateX(${(i - currentSlide) * 100}%)`
};
return (
<Fragment key={i}>
<div className="slide" style={styles}>
<img src={image} alt="random" />
</div>
</Fragment>
);
});
const mouseHandler = () => {
setAnimationState("paused");
};
const mouseHandlerLeave = () => {
setAnimationState("animate");
};
const animationEndHandler = nextButtonHandler;
const classes = `progress-bar ${animationState}`;
const containerClasses = `slider-container ${animationState}`;
return (
<>
<div className="container">
<div
className={containerClasses}
onMouseEnter={mouseHandler}
onMouseLeave={mouseHandlerLeave}
>
{slides}
<button className="btn btn-prev" onClick={prevButtonHandler}>
{" "}
{"<"}{" "}
</button>
<button className="btn btn-next" onClick={nextButtonHandler}>
{" "}
{">"}{" "}
</button>
<div
onAnimationEnd={animationEndHandler}
ref={progressRef}
role={"progressbar"}
className={classes}
/>
</div>
</div>
</>
);
}
CSS
/* Safari 4.0 - 8.0 */
@-webkit-keyframes width {
from {
width: 100%;
}
to {
width: 0%;
}
}
@keyframes width {
from {
width: 100%;
}
to {
width: 0%;
}
}
.container {
width: 100vw;
display: grid;
place-items: center;
}
.container .slider-container {
width: 100%;
max-width: 600px;
height: 400px;
position: relative;
overflow: hidden;
}
.container .slider-container .slide {
position: absolute;
width: 100%;
max-width: 600px;
height: 400px;
transition: all 0.5s;
}
.container .slider-container .slide:hover {
animation-play-state: paused;
}
.container .slider-container .slide img {
object-fit: cover;
width: 600px;
height: 400px;
}
.container .slider-container .progress-bar {
position: absolute;
height: 10px;
bottom: 0;
left: 0;
width: 100%;
background-color: #61dafb;
}
.container .slider-container .progress-bar.animate {
animation-play-state: running;
}
.container .slider-container .progress-bar.paused {
animation-play-state: paused;
}
.container .slider-container .btn {
height: 40px;
width: 40px;
position: absolute;
border-radius: 50%;
top: calc(50% - 20px);
z-index: 1;
background-color: #ffffff;
font-size: 18px;
}
.container .slider-container .btn:hover {
cursor: pointer;
transform: scale(1.1);
}
.container .slider-container .btn.btn-prev {
left: 10px;
}
.container .slider-container .btn.btn-next {
right: 10px;
}
Try this:
Demo URL:
https://codesandbox.io/s/nifty-meninsky-pkijf0
Remarks
If this answer was not satisfactory, or confusing, or did not answer what was asked, please comment so I can edit it.
It's worth mentioning that I'm using google translator to answer, I apologize for any inconvenience