Vue骨架屏在图片加载后为什么会闪一下消失?

西门晶晶 阅读 51

我在做商品列表页首屏优化时用了骨架屏,但发现图片加载完成后骨架屏会闪一下才被替换。我用了v-if/v-else控制切换,骨架屏和真实图片的宽高比例也保持一致了,但问题还是存在:


<div v-for="item in items" :key="item.id">
  <div v-if="item.loaded" class="product-image">
    <img @load="item.loaded = true" :src="item.url" />
  </div>
  <div v-else class="skeleton">骨架屏内容</div>
</div>

尝试过给img加loading=”lazy”和设置width/height固定值,但切换时仍然有0.5秒的闪烁。这种情况下应该怎样优化过渡效果?是不是骨架屏的结构需要更贴近真实内容布局?

我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
西门秋梓
这个问题很典型,不是骨架屏结构的问题,而是你用了 v-if 切换导致 DOM 完全替换,浏览器重绘造成的视觉闪动。Vue 的 v-if 是惰性销毁重建,哪怕宽高一致,中间也有一个 DOM 节点不存在的瞬间,人眼就能感知到“闪”。

你应该用 display: none 这类样式控制来过渡,而不是用 v-if。具体做法是让骨架和真实内容共存于同一个容器,通过 class 控制显隐。

你可以监听图片的 onload 事件,在回调里设置 loaded 状态,然后用 CSS 做 fade out 效果。关键是不要用 v-if/v-else 拆成两个 div,那样注定会闪。

改法很简单:

<div v-for="item in items" :key="item.id" class="product-item">
<div class="skeleton" v-show="!item.loaded">骨架内容</div>
<img
:src="item.url"
@load="handleImageLoad($event, item)"
:class="{ 'loaded': item.loaded }"
/>
</div>


然后加点样式过渡:

.product-item img {
opacity: 0;
transition: opacity 0.3s;
}
.product-item img.loaded {
opacity: 1;
}
.skeleton {
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
background: #f5f5f5;
}


JS 部分这样处理:

methods: {
handleImageLoad(e, item) {
item.loaded = true
// 可选:延迟移除骨架,避免太快造成抖动
this.$nextTick(() => {
e.target.style.opacity = 1
})
}
}


核心就是别用 v-if,用 opacity + transition 做视觉切换。骨架屏的结构确实也要尽量贴近真实布局,但前提是 DOM 不能被干掉。你之前写的 v-if 在图片 load 触发前就把 img 节点删了,等于是先空一帧再塞回去,不闪才怪。

顺便提一句,这种场景其实更适合用 IntersectionObserver 预加载,但那是另一个优化点了。先把切换动画稳住再说。
点赞 2
2026-02-09 10:05
❤英歌
❤英歌 Lv1
这个问题很常见,本质上是渲染切换时机的问题。骨架屏在图片加载完成时闪一下,核心原因是v-if切换时会先销毁骨架屏的DOM,等图片加载完成后再渲染真实内容,这个过程存在时间差。

性能上更合理的做法是使用v-show替代v-if/v-else结构。v-show通过CSS的display属性控制显示隐藏,不会销毁和重建DOM,切换更平滑。你可以把逻辑改成这样:

<div v-for="item in items" :key="item.id" class="product-item">
<div class="skeleton" v-show="!item.loaded">骨架屏内容</div>
<div class="product-image" v-show="item.loaded">
<img @load="item.loaded = true" :src="item.url" />
</div>
</div>


另外,为了进一步优化视觉体验,可以给骨架屏加一个淡出效果,让切换更柔和:

.skeleton {
transition: opacity 0.3s ease-out;
}

.skeleton[style*='display: none'] {
opacity: 0;
}


这样在骨架屏隐藏的时候,会有一个0.3秒的渐隐动画,减少突兀感。同时建议给img设置固定的宽高,防止图片加载后布局抖动:

img {
width: 100%;
height: 200px;
object-fit: cover;
}


如果骨架屏内容比较复杂,结构上建议尽量贴近真实布局,特别是文字的位置和样式。这样即使图片加载慢一点,用户也能看到接近真实内容的占位,提升预期感。性能上,这种结构也能减少重绘重排的次数。
点赞 3
2026-02-08 12:00