
本文介绍如何在 React 中为卡片选择状态切换(全量展示 ↔ 单张高亮)添加流畅的 CSS 过渡动画,避免 DOM 完全卸载导致的闪烁问题,并提供可复用、可取消的 setTimeout 动画控制方案。
本文介绍如何在 react 中为卡片选择状态切换(全量展示 ↔ 单张高亮)添加流畅的 css 过渡动画,避免 dom 完全卸载导致的闪烁问题,并提供可复用、可取消的 `settimeout` 动画控制方案。
在 React 中实现卡片选择状态的视觉过渡,核心挑战不在于逻辑控制,而在于保持 DOM 稳定性与动画可控性的平衡。你当前的实现通过条件渲染(!isServiceSelected ?
✅ 推荐方案:双层容器 + 状态驱动透明度过渡
保留所有卡片在 DOM 中,仅通过 opacity 和 pointer-events 控制可见性与交互,配合 CSS transition 实现自然淡入淡出:
/* style.css */
.services-row > div {
transition: opacity 0.4s ease-in-out, transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.services-row > div:not(.active) {
opacity: 0;
pointer-events: none;
transform: scale(0.95);
}
.services-row > div.active {
opacity: 1;
transform: scale(1);
}
对应 JSX 修改(关键:移除条件渲染,统一渲染所有卡片,仅通过 className 控制状态):
return (
<main><div>
{/* 公共头部始终显示 */}
<section classname="section services-section" id="services"><div classname="container">
<div classname="row">
<div classname="page-header">
<div classname="section-title">
<h2>Services</h2>
<p>Enhance efficiency, reliability, and quality with automated test solutions.</p>
</div>
</div>
</div>
<div classname="row services-row">
{services_data.map((item, index) => (
<div key="{index}" classname="{`service-wrapper" selectedserviceindex="==" index : onclick="{()"> handleServiceClick(index)}
>
<service name="{item.name}" imagename="{item.imageName}" classname="{item.className}" desc="{item.desc}"></service>
</div>
))}
</div>
</div>
</section>
</div>
</main>
);
? 优势:零 JS 动画逻辑、无 timeout 风险、支持浏览器原生硬件加速、可无缝衔接 transform 动画(如缩放、位移)。
Orderly Sdk React Hooks下载Orderly React SDK 钩子使用参考指南,包括 useOrderEntry、usePositionStream、useOrderbookStream、useCollateral 等。
⚠️ 进阶方案:分阶段状态切换(适用于复杂交互动效)
若需更精细控制(例如:旧卡片淡出 → 新卡片淡入),可引入中间状态 isTransitioning,并安全管理定时器:
const [selectedServiceIndex, setSelectedServiceIndex] = useState(null);
const [isServiceSelected, setIsServiceSelected] = useState(false);
const [isTransitioning, setIsTransitioning] = useState(false); // 新增过渡中状态
const timeoutRef = useRef(null);
const handleServiceClick = (index) => {
if (index = services_data.length) return;
// 若点击同一卡片,直接收起(无过渡)
if (isServiceSelected && selectedServiceIndex === index) {
setIsServiceSelected(false);
return;
}
// 启动过渡:先隐藏当前内容
setIsTransitioning(true);
timeoutRef.current = setTimeout(() => {
setSelectedServiceIndex(index);
setIsServiceSelected(true);
setIsTransitioning(false);
}, 400); // 与 CSS transition 时间一致
};
// 组件卸载时清理定时器(防止内存泄漏)
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
配合 CSS(作用于外层容器):
.selected-service {
opacity: 0;
transition: opacity 0.4s ease;
}
.selected-service.active {
opacity: 1;
}
并在渲染中使用:
{isServiceSelected ? (
<div classname="{`selected-service" :>
{services[selectedServiceIndex]}
</div>
) : (
<section classname="section services-section" id="services">
{/* ... 全量卡片渲染逻辑 */}
</section>
)}
? 关键注意事项
- 性能权衡:全量保留在 DOM 中适用于卡片数量较少(如 ≤ 10)的场景;若卡片含重型子组件(如图表、视频),建议结合 React.memo 或虚拟滚动优化。
- 无障碍访问:pointer-events: none 会禁用键盘焦点,务必补充 tabIndex="-1" 或改用 aria-hidden="true" + visibility: hidden 替代。
- 动画一致性:CSS transition 时间必须与 JS setTimeout 值严格匹配,否则出现卡顿或错位。
- 响应式适配:在移动端,可额外添加 scale 或 translateY 动画增强层次感,避免单一 opacity 显得平淡。
最终,选择哪种方案取决于你的设计需求:追求简洁稳定,用纯 CSS 透明度控制;追求电影级转场,用分阶段状态 + 可控定时器。二者均优于直接条件渲染——因为真正的动画,始于 DOM 的“存在”,而非“出现”。











