用 calc(-100% + 20px) 替代 translateY(-100vh) 更稳妥,配合 IntersectionObserver 触发、--vh 变量兜底 iOS 问题,并仅用 transform 和 opacity 实现 GPU 加速动画。

用 vh 做入场起点,但别直接写 transform: translateY(-100vh)
视口高度入场的核心是让元素“初始不可见”,滚动/加载后“滑入”。很多人第一反应是 transform: translateY(-100vh),但这样在小屏(比如 iPhone SE)上可能移出太多,大屏又不够——vh 是动态的,但入场距离得匹配内容真实高度。更稳妥的是用 calc(-100% + 20px) 或基于父容器的 height 计算,而不是硬绑 vh。
常见错误现象:animation 播放时元素闪一下才动,或根本不动——大概率是初始状态没脱离文档流或没触发重排。确保出发前有 opacity: 0 和 transform: translateY(20px) 这类明确起始值,且父容器不能是 overflow: hidden 截断了位移路径。
- 入场动画必须定义
@keyframes的from和to,不能只写to - 如果元素本身有
margin-top,它会和transform叠加,导致实际位移超出预期 - 移动端 Safari 对
vh的解析有延迟(尤其地址栏收起时),建议搭配resize事件重设--vh自定义变量
animation 触发时机:用 intersectionObserver 而不是 scroll 监听
靠监听 window.onscroll 判断是否进入视口,性能差、易抖动、兼容性弱。现代做法是用 IntersectionObserver,它原生支持阈值控制、懒初始化,且不阻塞主线程。
使用场景:长列表卡片、文章段落、页脚模块——只要需要“真正进入可视区再动”,就该用它,而不是等页面加载完统一播放。
立即学习“前端免费学习笔记(深入)”;
- 设置
threshold: 0.1表示元素 10% 高度进入视口即触发,比0.0更早响应 - 观察器回调里给元素加
class(如is-inview),动画用.element.is-inview { animation: slideUp 0.6s ease-out forwards; } - 别在回调里直接写
element.style.animation = '...',会覆盖 CSS 中定义的animation-fill-mode行为
vh 在 iOS Safari 下失效?用 --vh 变量兜底
iOS Safari 的 vh 单位在地址栏展开/收起时不会实时更新,导致“入场距离错位”——比如页面刚加载时 100vh 是 667px,地址栏一收变成 736px,但样式没重算。这是真坑,不是你写错了。
解决方案是用 JS 动态计算并注入 CSS 变量:document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`),然后在 CSS 里写 transform: translateY(calc(var(--vh) * -100))。
- 必须在
resize和orientationchange事件中重新设置--vh,否则横竖屏切换就崩 - 注意:
window.innerHeight在 iOS 上有时返回的是布局视口高,不是视觉视口,可加document.body.clientHeight做 fallback 判断 - 不要用
100dvh替代——它在旧版 Safari 完全不支持,连@supports都判不出
动画卡顿或跳帧?关掉 transform 外的属性动画
opacity 和 transform 是唯二能走 GPU 加速的 CSS 属性。一旦你在 @keyframes 里写了 top、left、margin 或 height,动画立刻降级到 CPU 渲染,60fps 就成奢望。
性能影响明显:低端安卓机上,含 margin-top 的入场动画常出现首帧延迟或中间掉帧;iOS 上甚至可能触发强制同步布局(layout thrashing)。
- 所有位移必须用
transform: translateY(),别碰盒模型属性 - 如果要配合尺寸变化(比如从 0 → 100% 宽),用
transform: scale()代替width - 加
will-change: transform, opacity仅在必要时(如复杂动画链),滥用反而增加内存开销
事情说清了就结束。最麻烦的其实是 iOS 的 vh 不一致 + IntersectionObserver 兼容性兜底 + GPU 加速边界这三件事叠在一起,单独看都不难,合起来调一次就得测四台设备。


















