KAIROS.WORKSPACE
技术宇宙 / ONLINE
返回踩坑地图
前端工程高级5 分钟

滚动进场动画把内容永久藏起来了:IntersectionObserver 的三个降级洞

用 IntersectionObserver 做渐入揭示时,打印、脚本禁用、观察者漏触发三种情况会让内容永久不可见,配自愈与打印兜底的解法。

#动效#IntersectionObserver#降级#可访问性

现象

一个用 [data-scroll-reveal] 做“进入视口渐入”的列表页:正常浏览一切优雅。但三个场景暴露了问题——

  1. 用户 Ctrl+P 打印或某些 PDF 快照工具抓取时,未滚动到的下方区块整块空白
  2. JS 被扩展或网络问题中断时,元素停在 CSS 初始的 opacity:0,读者看到一片“有字但看不见”的区域;
  3. 个别元素因布局时机(图片撑高后已全在视口外上方)永远不触发 IO 回调,永久隐藏。

根因是同一个:进场动画默认把“未揭示”设成了视觉不可见,却没有为“揭示逻辑没跑成”留后路

修复三件套

一、打印兜底——打印媒体里强制全部可见:

@media print {
  [data-scroll-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }
}

二、自愈定时器——任何环境下,超时未揭示就直接标记为已揭示,绝不永久隐藏:

window.setTimeout(() => {
  document
    .querySelectorAll('[data-scroll-reveal]:not([data-revealed])')
    .forEach((el) => markRevealed(el));
}, 6000);

三、reduced-motion / 无 IO 直接静态可见——初始化时判定,不注册观察者:

const prefersReduced = window.matchMedia(
  '(prefers-reduced-motion: reduce)',
).matches;
if (prefersReduced || !('IntersectionObserver' in window)) {
  document.querySelectorAll('[data-scroll-reveal]').forEach(markRevealed);
  return;
}

原则:动效是增强,不是内容显示的前提

一条通用铁律——内容的最终可见性不能依赖任何 JS 回调成功执行。CSS 的“初始隐藏态”必须同时满足:

  • 打印/PDF:覆盖为可见;
  • JS 关闭/报错:要么初始就可见(渐进增强),要么有 <noscript> 或超时兜底;
  • 减弱动效偏好:直接可见;
  • 观察器异常:自愈超时。

把进场动画想成“锦上添花的延迟出现”,而不是“内容的开关”。开关永远握在无 JS 也能成立的路径里。

验证

  • 浏览器开发面板切换 prefers-reduced-motion,确认全部区块立即可见;
  • 模拟 JS 抛错(控制台临时 throw),确认内容仍显示;
  • 打印预览确认无空白块。这三条都能用 Playwright 固化(本项目在 QA 轮做了 6 页 reduced-motion 免滚动检查,即基于此)。