直接用 opacity 动画切换背景图会闪屏,因 background-image 不支持透明度动画,单容器切换时新图未加载完即触发 opacity 变化,导致空白或残留;须用双层绝对定位容器预加载图片,通过 opacity 关键帧与 z-index 类控制实现平滑淡入淡出。

为什么直接用 opacity 动画切换背景图会闪屏
因为 CSS 背景图本身不支持 opacity 动画,你只能对容器元素做透明度变化。但若只用单个 <div> 切换 background-image 并加 opacity 动画,浏览器会在新图加载完成前显示空白或旧图残留,造成视觉跳变。
真正可行的方案是:用两个叠层容器,通过 z-index 控制显隐顺序,各自独立控制 opacity,让一张淡出、另一张淡入——前提是两张图都已加载完毕。
- 必须预加载下一张图(否则淡入时触发加载,必然卡顿或留白)
-
z-index不能靠动画控制(CSS 不支持z-index过渡),得靠@keyframes配合opacity触发层叠切换 - 两层需使用
position: absolute且尺寸完全一致,否则错位
用 @keyframes 同时驱动 opacity 和 z-index 的写法
注意:z-index 本身不可动画,但可以放在关键帧里“突变”。只要时机和层级逻辑对得上,人眼感知就是平滑切换。
@keyframes fadeSwitch {
0% {
opacity: 1;
}
25% {
opacity: 1;
}
75% {
opacity: 0;
}
100% {
opacity: 0;
}
}
<p>/<em> 主动层(当前显示) </em>/
.layer-active {
animation: fadeSwitch 4s forwards;
z-index: 2;
}</p><p>/<em> 待命层(下一张) </em>/
.layer-next {
opacity: 0;
z-index: 1;
animation: fadeSwitch 4s 0.1s forwards reverse;
}
-
reverse让待命层从opacity: 0开始、到opacity: 1结束 -
0.1s延迟确保两层动画不完全同步,避免中间出现双opacity: 0 -
z-index不写进动画,而用 class 控制初始值,靠动画结束后的 class 切换来更新层级
如何用 JS 安全切换并预加载下一张图
手动改 class 不够,关键是保证 layer-next 的 background-image 已加载完成再启动动画,否则淡入时仍是空白。
立即学习“前端免费学习笔记(深入)”;
推荐用 new Image() 预加载 + onload 回调:
function preloadImage(src) {
return new Promise(resolve => {
const img = new Image();
img.onload = () => resolve();
img.src = src;
});
}
<p>async function switchBackground(nextUrl) {
const nextLayer = document.querySelector('.layer-next');
nextLayer.style.backgroundImage = <code>url(${nextUrl})</code>;</p><p>await preloadImage(nextUrl); // 等加载完再动
document.querySelector('.layer-active').classList.remove('layer-active');
nextLayer.classList.add('layer-active');
nextLayer.classList.remove('layer-next');
document.querySelector('.layer-active').classList.add('layer-next');
}
- 不要依赖
img.complete判断,有些浏览器缓存下它可能为true但渲染未就绪 - 每次切换后记得交换两个 layer 的 class 名,为下一次切换做准备
- 如果图多,可维护一个预加载队列,提前载入后续 2–3 张
移动端 Safari 的 opacity 动画卡顿问题怎么破
iOS Safari 对非合成层的 opacity 动画性能极差,尤其全屏背景图这种大纹理。强制开启硬件加速能明显改善:
- 给两层都加上
transform: translateZ(0)或will-change: opacity - 避免同时 animating
opacity和background-position,后者会触发重绘 - 图片尺寸别超视口太多(比如用 4K 图铺满 iPhone,GPU 解码压力大)
- 用
image-set()或srcset提供适配分辨率的图,减少缩放开销
最易忽略的一点:动画 duration 别设太短(


















