background-attachment: fixed 在移动端无效,因 iOS Safari 和安卓 WebView 为性能主动禁用;推荐用 scroll + background-position 模拟或 scroll-driven animations 实现兼容视差效果。

background-attachment: fixed 在移动端根本不会生效
别试了——background-attachment: fixed 在 iOS Safari 和绝大多数安卓 WebView(包括微信内置浏览器)里,从很早开始就被浏览器主动禁用。你写的 CSS 没错,DevTools 里看到的 computed 值也确实是 scroll,不是你漏写了什么。原因很实在:固定背景需要持续冻结合成层,对移动设备 GPU 和内存压力太大,Chrome 自 v56、Safari 更早就默认降级处理。
常见错误现象:background-attachment: fixed 在 iPhone 上完全没反应;嵌套在 transform 或 overflow: hidden 的父容器里直接失效;加了 will-change: transform 反而更卡。
- 只在根元素(
body或html)上才相对稳定,但实际布局中几乎无法满足语义和结构要求 - 哪怕写在
section上,只要它上面有任意一层用了perspective、filter或opacity,fixed 就会退化 - 桌面端若想勉强跑通,必须同时满足:
height: 100vh(不能是min-height)、无 transform 父容器、显式声明background-size: cover和background-position: center
用 scroll + background-position 模拟最可靠
这是目前兼容性最好、可控性最强的方案,所有现代浏览器都支持,且能精准响应触控滚动、惯性滑动甚至键盘方向键。
核心逻辑是监听 window.scrollY,按比例缩放后赋给 background-position 的 Y 轴,让背景图移动得比内容慢。例如:center ${window.scrollY * 0.4}px。
立即学习“前端免费学习笔记(深入)”;
- 目标元素必须显式设
background-attachment: scroll(即使它是默认值),避免继承干扰 - 用
requestAnimationFrame包裹 scroll 回调,否则高频触发会导致明显卡顿 - 视差系数建议取 0.2~0.6;0.4 是较稳妥的中间值,太大易露白,太小无感
- 背景图尺寸至少为容器宽高的 1.5 倍,优先用
background-size: cover,不用百分比写法(如50% 50%),它基于锚点对齐,滚动中容易跳变
scroll-driven animations 是未来,但得检查支持性
Chrome 115+、Safari 17.4+ 支持纯 CSS 实现滚动驱动的背景位移,无需 JS,性能更好,还能响应滚速变化。
基础写法:animation: parallax 1s linear both; animation-timeline: scroll(root auto);,关键帧里只能用 background-position。
- 必须配合
@scroll-timeline规则定义时间轴,只写animation-timeline: scroll(root)不生效 - 容器需有明确滚动上下文(比如
body或设置了overflow-y: scroll的父元素) - 检测是否支持:
CSS.supports('animation-timeline', 'scroll()'),不支持时必须 fallback 到 JS 方案 - 一旦父容器存在
transform或overflow: hidden,background-position的参考系可能被截断,偏移量失准——这时改用transform: translateY()控制整个元素更稳
容易被忽略的细节:背景图尺寸和 reference frame
很多“视差不平滑”“滚动到一半突然跳一下”的问题,根源不在 JS 或 CSS 写法,而在背景图本身和容器的渲染上下文。
background-size: cover 是底线要求;如果图太小,background-position 移动时边缘立刻露白。更隐蔽的是 reference frame —— 当父容器有 transform、perspective 或 filter,background-position 的计算基准不再是视口,而是那个被截断的局部坐标系,导致位移量严重失真。
这种情况下,与其硬调 background-position,不如把背景图单独抽成一个 div,用 transform: translateY() 控制,参考系更干净,调试也直观。


















