如何在悬停时使动画播放状态暂停以在React中工作



我正在尝试构建一个图像转盘,其中进度条显示图像底部的剩余时间。转盘可以在给定的延迟后切换到下一个图像,但我无法在悬停时暂停动画。

https://codesandbox.io/s/unruffled-sun-qz5d89?file=/src/Slider.tsx

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 [timer, setTimer] = useState<NodeJS.Timer>();
const progressRef = useRef<HTMLDivElement | null>(null);
const [animationState, setAnimationState] = useState('animate');
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(() => {
if (progressRef.current) {
progressRef.current.style.transition = "none";
progressRef.current.style.width = "100%";
setTimeout(() => {
if (progressRef.current) {
progressRef.current.style.width = "0%";
progressRef.current.style.transition = `width ${slideShowDelay}s`;
}
}, 50);
}
}, [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 transitionEndHandler = 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 onTransitionEnd={transitionEndHandler} ref={progressRef} role={"progressbar"} className={classes} />
</div>
</div>
</>
)
}

我在这里做错了什么?感谢您的帮助。

问题


1。CSS中的转换和动画之间存在差异概念,在它们之间,有";暂停";(致speak(仅在动画中可用

注意到它可以模仿此功能(暂停转换(使用javascript,但如果我们只依赖CSS,这是不可能的。

参考:如何暂停CSS转换?

2.您使用的CSS属性适用于动画,并且没有转换,即:animation-play-state

重要提示


需要注意的是,可以删除css转换,只需删除转换属性或覆盖它,例如:transition:none!这很重要,但在移除过渡时,不可能恢复它,并且"动画"在此过程中丢失

解决方案


1。创建一个能够重新启动动画的函数,请注意这一点很重要,因为在第一个动画结束时(onAnimationEnd(,必须重新启动进度条的x秒计数,否则,它只能工作一次

注意:重新启动CSS动画的关键是将动画的动画名称设置为"none",然后将其设置回原始动画

示例:

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.每次更改幻灯片时调用此函数,无论是前一次还是前一次,例如:

第一个选项:

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]);

第二个选项:

或者更好的是,我们可以将startAnimation函数放在useEffect中,它将捕捉每张幻灯片的更改,因此无论幻灯片是来回的,应用程序都将正常工作。

示例:

useEffect(() => {
startAnimation();
}, [currentSlide, slideShowDelay]);

3.在CSS:中创建动画帧

@-webkit-keyframes width {
from {
width: 100%;
}
to {
width: 0%;
}
}
@keyframes width {
from {
width: 100%;
}
to {
width: 0%;
}
}

在上面的例子中,我们已经通过使用关键字";从";以及";至";(表示0%(开始(和100%(完成((。

4.使用onTransitionEnd更改为onAnimationEnd,例如:

<div
onAnimationEnd={animationEndHandler}
ref={progressRef}
role={"progressbar"}
className={classes}
/>

代码

打字
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;
}

试试这个:

演示URL:
https://codesandbox.io/s/nifty-meninsky-pkijf0

备注


如果这个答案不令人满意,或令人困惑,或没有回答所问的问题,请发表评论,以便我可以编辑它。

值得一提的是,我正在使用谷歌翻译来回答,对此给我带来的不便,我深表歉意

相关内容

  • 没有找到相关文章