Skeleton骨架屏在Vue中怎么实现动态高度?

轩辕淑怡 阅读 61

我用Vue做列表加载,想加个骨架屏,但每个item的高度不一样,写死height又不灵活。

试过用:style="{ height: loading ? '60px' : 'auto' }",但切换时会跳动,体验很差。有没有办法让骨架屏自动撑开真实内容的高度?

现在骨架屏组件是这样写的:

<template>
  <div v-if="loading" class="skeleton-item"></div>
  <div v-else class="real-content">{{ item.text }}</div>
</template>

<style scoped>
.skeleton-item {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}
</style>
我来解答 赞 6 收藏
二维码
手机扫码查看
2 条解答
俊衡
俊衡 Lv1
这个问题本质上是v-if/v-else切换时DOM销毁重建导致的高度突变。最直接的解决办法是让两者都渲染,通过CSS控制显隐而不是销毁DOM。

改一下你的组件结构:

<template>
<div class="item-wrapper">
<div class="skeleton-item" :class="{ 'is-hidden': !loading }"></div>
<div class="real-content" :class="{ 'is-hidden': loading }">{{ item.text }}</div>
</div>
</template>

<style scoped>
.skeleton-item {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
animation: shimmer 1.5s infinite;
border-radius: 4px;
min-height: 40px;
}

.real-content {
min-height: 40px;
}

.is-hidden {
display: none;
}
</style>


关键点在于给两边都加相同的min-height,这样切换时至少有个保底高度,不会突然塌陷。min-height的值根据你的实际内容来定,比如你之前试的60px就挺合适。

如果你的内容高度差异特别大(比如有的特别长),可以考虑用更智能的方案:在加载完成后把真实内容的高度缓存下来赋给骨架屏:

<template>
<div class="item-wrapper" :style="{ height: contentHeight }">
<div class="skeleton-item" v-show="loading"></div>
<div class="real-content" ref="contentRef" v-show="!loading">{{ item.text }}</div>
</div>
</template>

<script>
export default {
data() {
return {
loading: true,
contentHeight: '60px'
}
},
watch: {
loading(newVal) {
if (!newVal && this.$refs.contentRef) {
this.$nextTick(() => {
this.contentHeight = this.$refs.contentRef.offsetHeight + 'px'
})
}
}
}
}
</script>


不过这种方案第一次加载时还是会有轻微跳动,因为需要等内容渲染完才能拿到高度。第一种方案对于大多数场景已经够用了,简洁且效果还行。
点赞
2026-03-19 14:32
设计师康康
简单啊,先把真实内容渲染出来但不显示,把高度记下来再隐藏。省事的话直接这样改:

<template>
<div v-show="!loading" ref="content" style="opacity:0">{{ item.text }}</div>
<div v-if="loading" class="skeleton-item" :style="{height: contentHeight+'px'}"></div>
<div v-show="!loading" class="real-content">{{ item.text }}</div>
</template>

<script>
export default {
data() {
return {
contentHeight: 0
}
},
mounted() {
this.contentHeight = this.$refs.content?.clientHeight || 60
}
}
</script>


这样加载时骨架屏高度就和真实内容一致了,切换不会跳。懒得监听数据变化的话mounted里直接写死也行。
点赞 1
2026-03-07 20:38