纯css无法实现视口触发动画,必须用intersectionobserver检测元素进入视口后动态添加类名,再由css定义带animation-fill-mode: both的完整关键帧动画。

纯 CSS 无法做到。 这不是写法问题,而是能力边界——CSS 没有“视口可见性”这个运行时状态,@keyframes 和 animation 都不会感知滚动或元素是否出现在屏幕上。
为什么直接写 animation 不行
常见错误是给元素直接加 animation: slideIn 0.5s,结果动画在页面加载时就播完了,后续滚动进来毫无反应。原因很直接:浏览器解析完样式就立即启动动画,和元素是否可见、是否在视口里完全无关。
-
visibility: hidden或opacity: 0不会暂停动画,只影响渲染结果 -
animation-delay是固定延时,不能响应滚动时机 - 用
display: none更糟:元素脱离文档流,IntersectionObserver都观察不到
必须用 IntersectionObserver + 类名切换
这是当前唯一轻量、稳定、兼容主流浏览器(Chrome 60+/Firefox 55+)的方案。核心就是:JS 检测进入视口 → 动态加一个类 → CSS 用这个类定义动画。
- 观察器要设
threshold: 0.1,表示元素 10% 高度进入视口即触发,避免快速滚动错过 - 回调中调用
observer.unobserve(target),防止重复添加类导致动画卡顿或重播异常 - CSS 里必须写
animation-fill-mode: both,否则动画一结束就回退到初始透明度/位移,看起来像“闪一下” - 不要在 JS 里直接操作
element.style.animation,会覆盖 CSS 中定义的fill-mode行为
动画起始状态必须显式声明
很多“不动”或“闪一下”的问题,根源在初始样式没写全。比如想滑入,光写 to { transform: translateY(0); } 不够,from 必须明确:
/* 正确:起始状态清晰 */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.is-inview {
animation: slideIn 0.6s ease-out forwards;
animation-fill-mode: both;
}
- 别用
translateY(-100vh)做起点——小屏上移太多,大屏又不够,改用calc(-100% + 20px)更稳 - 父容器不能是
overflow: hidden,否则位移过程会被截断 - 避免混用
margin-top和transform,它们叠加后位移量不可控
真正麻烦的点不在代码多寡,而在状态同步:JS 要确保只在“首次进入且未播放过”时加类;CSS 要保证起始值、填充模式、关键帧都闭环;DOM 结构还得让 IntersectionObserver 能稳定取到目标节点——三者漏一,动画就断在半路。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











