React TSX点击触发动画及按钮控制动画进度的实现方案咨询
解决方案:点击触发CSS动画及分步控制动画进度
一、点击div启动动画并保持最终位置
修改思路
将动画从默认伪元素样式中剥离,通过添加类名触发动画;利用React状态管理类名切换,实现点击触发效果。forwards属性会让动画结束后停留在最终帧状态。
修改后代码
.tsx文件
import { useState } from 'react'; import styles from "./WaterTracker.module.css"; export default function WaterTracker() { const [isAnimating, setIsAnimating] = useState(false); const handleClick = () => { setIsAnimating(true); }; return ( <div className={styles.mainContainer}> <div className={styles.wrapper}> <div className={`${styles.mainContent} ${isAnimating ? styles.active : ''}`} onClick={handleClick} ></div> </div> </div> ); }
.css文件
:root { --background: #f8f8f8; --dark: #303032; } .mainContainer { box-sizing: border-box; margin: 0; padding: 0; } .wrapper { background: var(--background); min-height: 100vh; display: flex; justify-content: center; align-items: center; } .mainContent { position: relative; background: var(--dark); border: 0.25em solid var(--dark); height:8rem; width: 8rem; outline: 0.25; overflow: hidden; cursor: pointer; } .mainContent::before { content: "Drink je water"; font-size: 2rem; color: white; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; text-align: center; } .mainContent::after { content: ""; position: absolute; background: var(--background); height: 200%; width: 200%; bottom: -50%; left: -50%; border-radius: 45%; } .mainContent.active::after { animation: spin 6s ease-in-out forwards; } @keyframes spin { 0% { transform: translateY(0) rotate(0deg); } 100% { transform: translateY(-100%) rotate(500deg); } }
二、按钮控制动画的translateY分步进度
修改思路
使用CSS变量动态控制动画的最终translateY值,结合React状态管理当前进度;通过按钮点击更新目标值,并通过“移除再添加动画类”的方式强制动画重新执行。
修改后代码
.tsx文件
import { useState, useRef } from 'react'; import styles from "./WaterTracker.module.css"; export default function WaterTracker() { const [progress, setProgress] = useState(0); // 0:初始, 1:到-50%, 2:到-100% const contentRef = useRef<HTMLDivElement>(null); const getTargetY = () => { switch(progress) { case 1: return "-50%"; case 2: return "-100%"; default: return "0"; } }; const handleNextStep = () => { if (progress >= 2) return; // 强制重绘后重新触发动画 if (contentRef.current) { contentRef.current.classList.remove(styles.active); void contentRef.current.offsetWidth; contentRef.current.classList.add(styles.active); } setProgress(prev => prev + 1); }; return ( <div className={styles.mainContainer}> <div className={styles.wrapper}> <div ref={contentRef} className={styles.mainContent} style={{ "--target-y": getTargetY(), "--current-y": progress === 0 ? "0" : progress === 1 ? "-50%" : "-100%" } as React.CSSProperties} ></div> <button onClick={handleNextStep} className={styles.controlBtn}> {progress === 0 ? "开始填充" : progress === 1 ? "继续填充" : "已完成"} </button> </div> </div> ); }
.css文件
:root { --background: #f8f8f8; --dark: #303032; } .mainContainer { box-sizing: border-box; margin: 0; padding: 0; } .wrapper { background: var(--background); min-height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 2rem; } .mainContent { position: relative; background: var(--dark); border: 0.25em solid var(--dark); height:8rem; width: 8rem; outline: 0.25; overflow: hidden; } .mainContent::before { content: "Drink je water"; font-size: 2rem; color: white; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; text-align: center; } .mainContent::after { content: ""; position: absolute; background: var(--background); height: 200%; width: 200%; bottom: -50%; left: -50%; border-radius: 45%; } .mainContent.active::after { animation: spin 6s ease-in-out forwards; } @keyframes spin { 0% { transform: translateY(var(--current-y, 0)) rotate(0deg); } 100% { transform: translateY(var(--target-y, -100%)) rotate(500deg); } } .controlBtn { padding: 0.8rem 1.5rem; font-size: 1rem; border: none; border-radius: 4px; background: var(--dark); color: white; cursor: pointer; }
内容的提问来源于stack exchange,提问作者Luuk van Dijk
相关产品推荐
相关产品推荐

