React useState控制点击颜色正反keyframes动画 解决首次加载自动触发问题
问题根因
首屏加载时activeDiv初始值为false,代码会直接给::after伪元素绑定unsetColor反向动画,浏览器渲染元素时检测到animation属性存在有效值,会默认立即执行一次动画,这就是未点击就自动触发回退动画的核心原因。
之前尝试animation-play-state没有达到预期,是因为初始状态下动画本身就被直接绑定,默认状态就是running,单纯修改播放状态解决不了「绑定即执行」的逻辑问题。另外原代码中transform-origin: bottom center};末尾多了冗余右花括号,属于语法错误,也会干扰样式正常解析。
实现方案
方案1:最小改动修复原有Keyframes逻辑
核心思路是新增交互标记,避免首次加载时就给元素绑定反向动画,同时补全动画填充模式保证动画结束后状态不跳变:
import { useState } from 'react'; import styled, { css, keyframes } from 'styled-components'; export default function ClickAnimDiv() { const [activeDiv, setDivState] = useState(false); // 标记是否发生过用户点击,拦截初始动画 const [hasInteracted, setHasInteracted] = useState(false); const setColor = (colorTo) => keyframes` 0% {transform: scaleY(0); } 100% {transform: scaleY(1); background-color: ${colorTo}} `; const unsetColor = (colorFrom) => keyframes` 0% {transform: scaleY(1); background-color: ${colorFrom}} 100% {transform: scaleY(0); background-color: transparent;} `; const BigDiv = styled("div")( ({ theme, activeDiv }) => css` min-width: 220px; height: 100%; position: relative; padding: ${theme?.spacing?.(2, 0) || '16px 0'}; border-bottom: red 4px solid; background-color: transparent; cursor: pointer; text-align: center; will-change: transform; ::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; background-color: transparent; z-index: 0; /* 首次未交互时不绑定任何动画,交互后根据状态切换对应动画 */ animation: ${ !hasInteracted ? 'none' : activeDiv ? `${setColor("red")} 0.5s ease forwards` : `${unsetColor("red")} 0.5s ease forwards` }; transform-origin: bottom center; } ` ); const SecondDiv = styled("div")( () => css` display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; position: relative; z-index: 1; ` ); return ( <BigDiv activeDiv={activeDiv} onClick={() => { if (!hasInteracted) setHasInteracted(true); setDivState(!activeDiv); }} > <SecondDiv> <p>Click to animate</p> </SecondDiv> </BigDiv> ); }
方案2:Transition实现(更推荐的最优方案)
这类双向状态切换的缩放+变色效果,不需要编写两套Keyframes,用CSS transition 就能实现更流畅稳定的效果,从根源上避免初始动画误触发的问题,代码也更易维护:
import { useState } from 'react'; import styled, { css } from 'styled-components'; export default function ClickAnimDiv() { const [activeDiv, setDivState] = useState(false); const BigDiv = styled("div")( ({ theme, activeDiv }) => css` min-width: 220px; height: 100%; position: relative; padding: ${theme?.spacing?.(2, 0) || '16px 0'}; border-bottom: red 4px solid; background-color: transparent; cursor: pointer; text-align: center; ::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; z-index: 0; /* 直接通过状态控制最终样式 */ background-color: ${activeDiv ? "red" : "transparent"}; transform: scaleY(${activeDiv ? 1 : 0}); transform-origin: bottom center; /* 统一设置过渡效果,自动处理双向动画 */ transition: all 0.5s ease; } ` ); const SecondDiv = styled("div")( () => css` display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; position: relative; z-index: 1; ` ); return ( <BigDiv activeDiv={activeDiv} onClick={() => setDivState(!activeDiv)} > <SecondDiv> <p>Click to animate</p> </SecondDiv> </BigDiv> ); }
该方案不需要额外的交互标记状态,初始状态下scaleY(0)+透明背景本身就是预期的初始样式,点击切换状态时transition会自动识别属性变化生成平滑过渡,不会出现首屏自动执行动画的问题。
内容的提问来源于stack exchange,提问作者P H
相关产品推荐
相关产品推荐

