transition不会意外触发,其仅响应属性值变化;页面初载动画实为animation、JS改类或FOUC所致,需通过基础类设初始态、声明transition、JS手动触发并避免动态移除transition来实现可控入场。

它根本不会“意外触发”——你看到的加载时动画,其实是 animation 在工作,或者 JS 在 DOM 就绪后悄悄改了类,而浏览器渲染时机没卡准。真正的 transition 只响应属性值变化,没有变化就没有过渡。
为什么页面一打开就动了,却不是 transition 干的
常见现象:元素在 HTML 渲染完成瞬间淡入、上滑或旋转。这不是 transition 自动播放,而是以下之一:
- 你写了
@keyframes+animation,并漏了animation-fill-mode: forwards,导致播完回退又“闪一下” - JS 在
DOMContentLoaded里批量加.loaded类,但此时 DOM 已绘制完毕,用户先看到初始态(比如opacity: 0),再立刻变到opacity: 1,形成“先闪后动” - 外部 CSS 文件通过
<link>异步加载,HTML 先按无样式渲染一帧,CSS 注入后触发属性重算,transition被“补上”——本质是 FOUC
用 transition 实现可控的首次入场,必须手动控制起始态
想让一个卡片淡入+上滑,得靠 JS 触发,且必须确保浏览器有明确的“从哪来、到哪去”:
- 基础样式中必须显式写死初始值:
.card { opacity: 0; transform: translateY(20px); } -
transition必须声明在基础类里,不能只写在.card.active中:.card { transition: opacity 0.4s ease, transform 0.4s ease; } - JS 添加类前,可选加
requestAnimationFrame确保样式变更不被合并:requestAnimationFrame(() => el.classList.add('active')) - 避免在添加类后立刻读取
getComputedStyle(el).opacity,这会强制同步重排,打断过渡
Next.js / React 场景下,状态恢复导致的“多动一次”
从 Cookie 或 localStorage 恢复 visible 状态时,若过渡类(如 transition-all duration-300)和状态类(如 opacity-0)同时存在,就会在 hydration 后立即触发动画。解决关键是分离控制权:
立即学习“前端免费学习笔记(深入)”;
- 用两个独立 state:
const [visible, setVisible] = useState(false)(从 Cookie 读取)和const [transitionEnabled, setTransitionEnabled] = useState(false)(仅交互时设为 true) - 过渡类只在
transitionEnabled为 true 时才加入:className={`... ${transitionEnabled ? 'transition-all duration-300' : ''}`} -
useEffect初始化 visible 后,不碰transitionEnabled;等用户第一次点击,再setTransitionEnabled(true),后续所有切换都带过渡
最易忽略的坑:transition 声明被动态移除
很多人把 transition 写在状态类里,比如 .fade-in { transition: opacity 0.3s; opacity: 1; },然后用 JS 切换 .fade-in 和 .fade-out。一旦 .fade-in 被移除,transition 就没了,下次加回来时浏览器已丢失起始值,直接跳变。
正确做法是把 transition 抽到基础类中,永远不删:.el { transition: opacity 0.3s ease; },再用 .el--hidden { opacity: 0; } 和 .el--shown { opacity: 1; } 控制值。这样无论怎么切类,过渡链始终在线。


















