
本文介绍如何通过 css opacity 过渡与状态协调控制,实现卡片列表与单张选中卡片之间的平滑淡入淡出效果,避免 dom 完全替换导致的动画中断问题。
本文介绍如何通过 css opacity 过渡与状态协调控制,实现卡片列表与单张选中卡片之间的平滑淡入淡出效果,避免 dom 完全替换导致的动画中断问题。
在 React 中实现卡片选择态的视觉过渡,关键在于保持 DOM 结构稳定,而非条件渲染({condition ? <A /> : <B />})导致元素频繁挂载/卸载——这会使 CSS transition 失效,因为浏览器无法对“不存在的元素”执行渐变动画。
✅ 推荐方案:统一容器 + 状态驱动透明度
将所有卡片(列表模式)和选中卡片(详情模式)保留在同一 DOM 容器中,通过 opacity 和 pointer-events 配合 transition 控制可见性与交互,而非 display: none 或条件移除节点。
使用 @ainative/react-sdk 为 React 应用添加 AI 聊天和积分。适用于 (1) 安装 @ainative/react-sdk,(2) 使用 useChat hook 实现聊天完成。
核心思路
- 始终渲染 <section className="services-section">(含全部卡片)和 <div className="selected-service">(当前选中卡片)
- 使用 opacity + transition 实现淡入淡出
- 用 pointer-events: none 暂时禁用非活跃区域的点击,防止误触
- 利用 useEffect 清理可能残留的 setTimeout,确保组件卸载安全
示例代码(优化后的组件结构)
const App = () => {
const [selectedServiceIndex, setSelectedServiceIndex] = useState(null);
const [isServiceSelected, setIsServiceSelected] = useState(false);
const [isTransitioning, setIsTransitioning] = useState(false); // 控制过渡阶段
const handleServiceClick = (index) => {
if (index < 0 || index >= services_data.length) return;
if (isServiceSelected && selectedServiceIndex === index) {
// 取消选中:先淡出,再隐藏
setIsTransitioning(true);
setTimeout(() => {
setIsServiceSelected(false);
setIsTransitioning(false);
}, 300);
} else {
// 切换选中:先淡出当前视图,再更新索引并淡入
setIsTransitioning(true);
setTimeout(() => {
setSelectedServiceIndex(index);
setIsServiceSelected(true);
setIsTransitioning(false);
}, 300);
}
};
const services = services_data.map((item, index) => (
<div key={index} onClick={() => handleServiceClick(index)}>
<Service
name={item.name}
imageName={item.imageName}
className={item.className}
desc={item.desc}
/>
</div>
));
return (
<main>
{/* 始终存在 —— 列表视图(默认显示,选中时淡出) */}
<section
className="section services-section"
id="services"
style={{
opacity: isServiceSelected && !isTransitioning ? 0 : 1,
pointerEvents: isServiceSelected && !isTransitioning ? 'none' : 'auto',
transition: 'opacity 0.3s ease, pointer-events 0.3s step-end'
}}
>
<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}</div>
</div>
</section>
{/* 始终存在 —— 详情视图(默认隐藏,选中时淡入) */}
<div
className="selected-service"
style={{
opacity: isServiceSelected && !isTransitioning ? 1 : 0,
pointerEvents: isServiceSelected && !isTransitioning ? 'auto' : 'none',
transition: 'opacity 0.3s ease, pointer-events 0.3s step-end',
// 可选:添加 transform 提升性能
willChange: 'opacity'
}}
>
{isServiceSelected && selectedServiceIndex !== null && services[selectedServiceIndex]}
</div>
</main>
);
};? 关键注意事项
- 不要用 display: none:它会触发重排(reflow),且无法过渡;opacity 是可过渡属性。
- pointer-events: none 的时机很重要:配合 transition 的 step-end 时间函数,确保在 opacity=0 时才禁用交互,避免点击穿透。
- will-change: 'opacity':提示浏览器对该属性做优化(尤其在复杂页面中提升动画流畅度)。
- 清理副作用:若使用 useEffect + setTimeout,务必在 useEffect 清理函数中调用 clearTimeout,防止内存泄漏或状态更新到已卸载组件。
- 可扩展性建议:如需更复杂的交互动画(如缩放、位移),可结合 react-transition-group 或 framer-motion,但本方案纯 CSS + React state 即可满足基础需求。
该方案兼顾简洁性、可维护性与用户体验——既保留了语义清晰的 DOM 结构,又实现了专业级的视觉反馈,是 React 卡片切换场景下的稳健实践。

















