iPhone / iPad 上网页动画不生效的排查与修复

ShuaiQi
#前端#Safari#CSS

一个在 Chrome 上跑得很顺的页面,拿到 iPhone 或 iPad 上打开,动画全部静止、部分视觉效果消失。这类问题不是玄学,原因基本集中在几处,按顺序排查一般都能定位。

第一步:确认是「没触发」还是「没渲染」

打开桌面的 Safari,用「开发 → 你的设备」连接 iPhone/iPad,检查元素的 animation-name 和 animation-play-state:

js
const el = document.querySelector(".quote-char");
getComputedStyle(el).animationName; // 期望不是 "none"
getComputedStyle(el).animationPlayState; // 期望不是 "paused"

如果 animationName 是 none,说明样式根本没匹配上,看第二步;如果有值但画面不动,看第三步。

原因一:动效全写在 hover 里

这是最常见也最容易被忽略的一条。

css
@media (hover: hover) {
  .card:hover {
    transform: translateY(-4px);
  }
}

触屏设备上 (hover: hover) 永远不匹配,因为手指没有「悬停」这件事。于是你为桌面精心设计的悬浮、流光、缩放,在手机和平板上一次都不会触发。

修复方式:给触屏补 :active 点按反馈,效果不用和 hover 一样,只要让用户知道点到了。

css
@media (hover: none) {
  .card:active {
    transform: scale(0.98);
  }
  .card:active::after {
    transform: translateX(130%);
  }
}

原因二:用 blur 滤镜做动画

css
@keyframes charIn {
  from { opacity: 0; filter: blur(6px); transform: translateY(0.3em); }
  to   { opacity: 1; filter: blur(0);   transform: translateY(0); }
}

单个元素没问题,但如果这段动画同时作用在几十个字上(逐字入场),iOS 会为每个元素单独建模糊图层,GPU 直接顶不住。表现就是动画卡住不动、或者只播放第一帧。

修复方式:小屏或触屏设备换成不带 blur 的关键帧。

css
@keyframes charInLite {
  from { opacity: 0; transform: translateY(0.3em); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (max-width: 768px), (hover: none) {
  .char { animation-name: charInLite; }
}

注意断点要用 (hover: none) 兜底,因为 iPad 竖屏宽度有 810px,纯 max-width: 768px 盖不到。

原因三:超大模糊背景层

filter: blur(64px) 加 conic-gradient 再加无限旋转,在桌面上很顺滑,在 A10 这类老芯片上就是灾难。这类装饰层还会和上面的文字动画抢 GPU。

修复方式:降低模糊半径、缩小元素尺寸,并提示浏览器提升为独立合成层。

css
@media (hover: none) {
  .aurora {
    inset: -35%;
    filter: blur(40px);
  }
  .orb {
    filter: blur(30px);
  }
}
.aurora, .orb {
  will-change: transform;
}

will-change 不要滥用,只加在确实在动的少数元素上,加多了反而更卡。

原因四:类名没定义

听起来很低级,但真的常见:改版时模板里用了 page-fade-up、quote-hero 这类新类名,CSS 里却忘了写。桌面端因为滚动、hover 的干扰不容易看出来,移动端一看就是「什么动画都没有」。

排查方法很直接,构建产物里搜一下:

bash
grep -o "page-fade-up" out/_next/static/chunks/*.css

搜不到就说明这个类不存在。

原因五:用户开了「减弱动态效果」

iOS 的「设置 → 辅助功能 → 动态效果 → 减弱动态效果」打开后,prefers-reduced-motion: reduce 会生效。如果你的 CSS 里有:

css
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; }
}

那动画「不动」其实是正确行为,不是 bug。测试前先确认系统设置。

原因六:fill-mode 残留 transform

css
.animate-card {
  animation: fadeUp 0.6s ease-out forwards;
}

forwards 会让动画结束后保留 transform,如果这个盒子里有 position: fixed 的子元素(比如弹窗遮罩),会被这个 transform 困在局部坐标系里,弹窗位置就跑到奇怪的地方。

修复方式:用 backwards 代替 forwards,动画结束回到自然状态;或者把 fixed 元素通过 portal 挂到 body。

排查顺序小结

  1. 用 Safari 远程调试确认 animationName 是否为 none
  2. 检查动效是否被包在 @media (hover: hover) 里
  3. 检查关键帧里是否用了 filter: blur()
  4. 检查背景装饰层的模糊半径是否过大
  5. 检查类名是否真的存在于构建产物
  6. 检查设备是否开启了减弱动态效果

按这个顺序走,绝大多数「iOS 上动画不动」都能定位。

常见问题

为什么只有 iPad 有问题,iPhone 正常?

iPad 的视口宽度更大,可能落在了不同的媒体查询断点里;另外老款 iPad 的 GPU 性能往往弱于同代 iPhone。

加了 will-change 反而更卡?

will-change 会强制创建合成层,占用显存。只给真正在动的元素加,动画结束后可以移除。

能不能直接在 iOS 上关掉所有动画?

可以,但体验会变差。更好的做法是保留轻量动画(透明度、位移),去掉高开销的部分(模糊、滤镜、大面积渐变)。

Safari 远程调试连不上?

需要 iOS 设备在「设置 → Safari → 高级」里开启 Web 检查器,并用数据线连接,电脑端 Safari 的「开发」菜单里才会出现设备。

相关阅读

评论