React状态变更时实现文本对象淡入淡出动画的相关问题
React 价格文本变更动画实现方案
现有代码问题
- 单个
updateNotificationRef被绑定到循环生成的多个<td>元素上,最终ref只会指向最后一个价格单元格,前两个价格变更时无法触发动画 - 动画关键帧
opacity: [0, 1, 0]执行结束后元素透明度会变为0,和“最终展示新价格”的需求冲突
方案1:修改现有Web Animations API逻辑
调整后代码如下:
export const MatchPrices = () => { const markets = useStore($markets); // 用数组存储所有价格单元格的ref const priceRefs = useRef([]); useEffect(() => { if (markets.length === 0) return; // 遍历所有价格元素触发动画 priceRefs.current.forEach(el => { if (!el) return; el.animate( { // 动画逻辑:原有透明度1 -> 降到0 -> 回到1(淡出后淡入) opacity: [1, 0, 1], // 可选加高亮效果:短暂变黄 backgroundColor: ["transparent", "#fff3cd", "transparent"] }, { duration: 800, easing: "ease", // 动画结束后保持最终状态 fill: "forwards" } ); }); }, [markets]); // 每次渲染前清空ref数组 priceRefs.current = []; return ( <Card className="price-display"> <Table className="card-table prices-table" size="sm"> <thead className="card-header"> <tr> <th>Home</th> <th>Draw</th> <th>Away</th> </tr> </thead> <tbody> <tr> {markets.length !== 0 ? ( markets .filter((market) => market.mt === "MATCH_WINNER")[0] .selections.map((selection, index) => ( <td className="match-price" key={selection.id} // 给每个元素绑定单独的ref ref={el => priceRefs.current[index] = el} > {selection.price.toFixed(2)} </td> )) ) : ( <td colSpan={3} style={{textAlign: 'center'}}> <Spinner animation="border" role="status" style={{ marginLeft: "Auto", marginRight: "Auto", }} ></Spinner> </td> )} </tr> </tbody> </Table> </Card> ); };
方案2:CSS类触发动画(更推荐,兼容性更好)
先添加CSS样式:
.match-price { transition: all 0.3s ease; } /* 变更动画类 */ .match-price.price-updating { animation: priceChange 0.8s ease forwards; } @keyframes priceChange { 0% { opacity: 1; background: transparent; } 40% { opacity: 0; background: #fff3cd; } 100% { opacity: 1; background: transparent; } }
调整React逻辑,不需要维护ref数组,直接通过类名控制动画触发:
export const MatchPrices = () => { const markets = useStore($markets); useEffect(() => { if (markets.length === 0) return; // 选中所有价格单元格 const priceEls = document.querySelectorAll(".match-price"); priceEls.forEach(el => { // 先移除动画类,保证下次变更可以重复触发 el.classList.remove("price-updating"); // 强制重绘,确保类名移除生效后再加回 void el.offsetWidth; el.classList.add("price-updating"); // 动画结束后移除类 setTimeout(() => el.classList.remove("price-updating"), 800); }); }, [markets]); return ( <Card className="price-display"> <Table className="card-table prices-table" size="sm"> <thead className="card-header"> <tr> <th>Home</th> <th>Draw</th> <th>Away</th> </tr> </thead> <tbody> <tr> {markets.length !== 0 ? ( markets .filter((market) => market.mt === "MATCH_WINNER")[0] .selections.map((selection) => ( <td className="match-price" key={selection.id} > {selection.price.toFixed(2)} </td> )) ) : ( <td colSpan={3} style={{textAlign: 'center'}}> <Spinner animation="border" role="status" style={{ marginLeft: "Auto", marginRight: "Auto", }} ></Spinner> </td> )} </tr> </tbody> </Table> </Card> ); };
内容的提问来源于stack exchange,提问作者clattenburg cake
相关产品推荐
相关产品推荐

