news 2026/9/18 19:20:09

Element UI Skeleton 骨架屏组件完全指南:从基础用法到源码级防闪烁优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Element UI Skeleton 骨架屏组件完全指南:从基础用法到源码级防闪烁优化

Element UI Skeleton 骨架屏组件完全指南:从基础用法到源码级防闪烁优化

【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element

Skeleton(骨架屏)是 Element UI 提供的加载占位组件,用于在数据请求尚未返回时,以接近真实界面的灰色占位结构替代空白页面,显著改善用户的等待体验。本文基于 Element UI 官方文档 examples/docs/en-US/skeleton.md 展开,并结合组件源码与样式实现,系统讲解el-skeletonel-skeleton-item的完整用法、属性语义、插槽机制以及throttle防闪烁的底层原理,帮助你构建真正可用、性能友好且不抖动的加载骨架。

为什么需要骨架屏:Skeleton 的适用场景

当页面加载数据时,用户看到的往往是整片空白或单调的 spinner。Skeleton 组件在数据到达之前,用一组灰色的矩形、圆角块与图片占位符模拟真实 DOM 的轮廓,让用户预判内容结构,从而获得更丰富的视觉与交互体验。Element UI 的骨架屏由两个组件协作完成:

  • el-skeleton:外层容器,负责loadingcountrowsanimatedthrottle等全局状态;
  • el-skeleton-item:单个骨架单元,通过variant指定矩形、圆形、按钮、图片等不同形态。

两个组件在源码中分别对应 packages/skeleton/src/index.vue 与 packages/skeleton/src/item.vue,并通过 packages/skeleton/index.js 以Vue.component的方式全局注册,组件名分别为ElSkeletonElSkeletonItem

基础用法:一行代码开启骨架占位

最基本的骨架屏只需在模板中放置一个<el-skeleton />

<template> <el-skeleton /> </template>

不传任何属性时,组件使用默认值渲染:loadingtrue(显示骨架)、rows4(渲染 4 行段落)、count1(只渲染一组)。从 index.vue 的props定义可以看出,这些默认值均由源码直接声明:

props: { animated: { type: Boolean, default: false }, count: { type: Number, default: 1 }, rows: { type: Number, default: 4 }, loading: { type: Boolean, default: true }, throttle: { type: Number, default: 0 } }

默认渲染的 4 行段落并非等宽:模板渲染逻辑(index.vue)会给首行添加is-first类、末行添加is-last类。对应样式 packages/theme-chalk/src/skeleton-item.scss 规定:

  • 首行(is-first)宽度为33%
  • 末行(is-last)宽度为61%
  • 中间段落(el-skeleton__paragraph)宽度为100%

这种「首行短、末行次短、中间占满」的排布正是对真实段落文本行高参差的模拟,官方文档称之为「rendering a title row with 33% width of the others」(渲染一个宽度为其余行 33% 的标题行)。

可配置行数:用 rows 控制段落数量

当默认的 4 行段落不符合需求时,可以通过rows属性自定义:

<el-skeleton :rows="6" />

rows只在未提供template插槽时生效(详见属性表),其类型为 number、默认值为4。注意rows渲染的是variant="p"的段落单元,并不影响下方将要介绍的el-skeleton-item自定义结构。

加载动画:用 animated 启用流光效果

默认骨架块是静态的灰块,而animated属性可以为其加上来回扫过的「流光」渐变动画:

<el-skeleton :rows="6" animated />

animatedtrue时,外层容器会追加is-animated类(见 index.vue)。动画的真正实现位于样式 packages/theme-chalk/src/skeleton.scss:通过skeleton-colormixin 生成 90 度线性渐变背景,背景尺寸放大为400% 100%,再利用el-skeleton-loading关键帧让背景位置从100% 50%平移到0 50%,以1.4s ease无限循环:

background: linear-gradient( 90deg, $--skeleton-color 25%, $--skeleton-to-color 37%, $--skeleton-color 63% ); background-size: 400% 100%; animation: #{$namespace}-skeleton-loading 1.4s ease infinite;

两个渐变色变量$--skeleton-color$--skeleton-to-color定义在 packages/theme-chalk/src/common/var.scss 中,可通过主题定制覆盖,这意味着你可以让骨架动画颜色跟随项目主题风格。

自定义模板:用 template 插槽 + variant 搭建真实结构

Element 内置的骨架模板只覆盖最常见场景,当页面结构复杂时,应使用template插槽自行拼装骨架,并结合不同variantel-skeleton-item组合出最接近真实 UI 的占位结构:

<template> <el-skeleton style="width: 240px"> <template slot="template"> <el-skeleton-item variant="image" style="width: 240px; height: 240px;" /> <div style="padding: 14px;"> <el-skeleton-item variant="p" style="width: 50%" /> <div style="display: flex; align-items: center; justify-items: space-between;" > <el-skeleton-item variant="text" style="margin-right: 16px;" /> <el-skeleton-item variant="text" style="width: 30%;" /> </div> </div> </template> </el-skeleton> </template>

该示例用一张图片占位块(240×240)模拟封面图,再用ptext单元模拟标题和描述行,正是卡片类页面的常见骨架形态。官方文档特别强调了一个容易被忽略的实战要点:自定义骨架时应尽量让结构与真实 DOM 高度一致("structuring them as closer to the real DOM as possible"),以避免因骨架与真实内容高度差导致的 DOM 跳动(DOM bouncing)。

variant 支持的骨架单元形态

el-skeleton-itemvariant属性决定了骨架单元的渲染形态,取值与实现对应关系如下(样式见 skeleton-item.scss):

variant渲染形态默认尺寸(源码实现)
p段落块高 16px,首行 33%、末行 61%、其余 100% 宽
h1大标题$--font-size-extra-large(20px)
h3中标题$--font-size-large(18px)
h5小标题$--font-size-medium(16px)
text文本行$--font-size-small(13px),宽 100%
caption说明文字$--font-size-extra-small(12px)
button按钮块高 40px、宽 64px、圆角 4px
image图片占位宽高自适应,内部渲染 SVG 占位图
circle圆形占位圆角 50%,尺寸对应头像中/大/小三种规格
rect矩形占位默认 16px 高、100% 宽、基础圆角

值得说明的是:h5虽未在官方文档 API 表中列出,但源码 skeleton-item.scss 已为其定义了样式,属于从源码结构可以确认的扩展形态。circle的尺寸变量$--avatar-medium-size$--avatar-large-size$--avatar-small-size复用了 Avatar 组件的尺寸体系,可推断其设计意图是模拟用户头像占位。

variant="image"是一个特殊实现:它并非纯 CSS 灰块,而是通过 item.vue 中的条件渲染,挂载了 packages/skeleton/src/img-placeholder.vue 提供的内联 SVG 图片占位图(一座山峰与太阳的剪影),SVG 填充色为$--svg-monochrome-grey并占据单元 22% 的宽高,视觉上更贴近「图片未加载」的语义。

加载状态切换:用 loading + default 插槽呈现真实内容

骨架屏的最终使命是「加载完成后展示真实 UI」。通过loading属性控制骨架 DOM 与真实 DOM 的切换,真实内容放入default插槽:

<template> <div style="width: 240px"> <p> <label style="margin-right: 16px;">Switch Loading</label> <el-switch v-model="loading" /> </p> <el-skeleton style="width: 240px" :loading="loading" animated> <template slot="template"> <el-skeleton-item variant="image" style="width: 240px; height: 240px;" /> <div style="padding: 14px;"> <el-skeleton-item variant="h3" style="width: 50%;" /> <div style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;" > <el-skeleton-item variant="text" style="margin-right: 16px;" /> <el-skeleton-item variant="text" style="width: 30%;" /> </div> </div> </template> <template> <el-card :body-style="{ padding: '0px', marginBottom: '1px' }"> <img src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png" class="image" /> <div style="padding: 14px;"> <span>Delicious hamberger</span> <div class="bottom card-header"> <span class="time">{{ currentDate }}</span> <el-button type="text" class="button">Operation button</el-button> </div> </div> </el-card> </template> </el-skeleton> </div> </template> <script> export default { data () { return { loading: true, currentDate: '2021-06-01' } }, } </script>

该示例中loading默认true,页面先显示骨架;当用户拨动开关将loading置为false,组件立刻切换到default插槽中真实的卡片内容。从 index.vue 的模板结构可以看到切换的本质是v-if="uiLoading"条件渲染:uiLoading为真时渲染骨架容器(外层el-skeleton+is-animated类),为假时渲染default插槽内容。

列表数据渲染:用 count 批量生成骨架

骨架屏最常见的场景是列表加载:数据未返回时,先渲染多条骨架占位,让页面看起来「正在逐条加载」。count属性用于控制渲染几组骨架模板:

<template> <div style="width: 400px"> <p> <el-button @click="setLoading">Click me to reload</el-button> </p> <el-skeleton style="width:400px" :loading="loading" animated :count="3"> <template slot="template"> <el-skeleton-item variant="image" style="width: 400px; height: 267px;" /> <div style="padding: 14px;"> <el-skeleton-item variant="h3" style="width: 50%;" /> <div style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;" > <el-skeleton-item variant="text" style="margin-right: 16px;" /> <el-skeleton-item variant="text" style="width: 30%;" /> </div> </div> </template> <template> <el-card :body-style="{ padding: '0px', marginBottom: '1px' }" v-for="item in lists" :key="item.name" > <img :src="item.imgUrl" class="image multi-content" /> <div style="padding: 14px;"> <span>Delicious hamberger</span> <div class="bottom card-header"> <span class="time">{{ currentDate }}</span> <el-button type="text" class="button">Operation button</el-button> </div> </div> </el-card> </template> </el-skeleton> </div> </template> <script> export default { data() { return { loading: true, currentDate: '2021-06-01', lists: [], } }, mounted() { this.loading = false this.lists = [ { imgUrl: 'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg', name: 'Deer', }, { imgUrl: 'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg', name: 'Horse', }, { imgUrl: 'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg', name: 'Mountain Lion', }, ] }, methods: { setLoading() { this.loading = true setTimeout(() => (this.loading = false), 2000) }, }, } </script>

示例中用:count="3"渲染 3 组相同的骨架,数据到达后mounted中将loading置为false并填充 3 条真实列表数据;点击按钮则通过setLoading先置true(重新显示骨架)再于 2 秒后切回真实列表,模拟一次完整的下拉刷新。源码层面,count通过 index.vue 的v-for="i in count"循环包裹template插槽实现多组渲染。

官方文档在此处给出了明确的性能提示:不建议渲染大量假 UI,因为假骨架同样占用浏览器渲染资源,且切换销毁时成本更高。请尽量让count保持最小,以换取更好的用户体验。

防闪烁优化:throttle 延迟渲染的真实原理

当接口响应极快时,骨架刚渲染到 DOM 就要立刻切换回真实内容,会造成一帧「白闪」或「闪烁」(sudden flashy)。throttle属性正是为此设计——它设置骨架渲染的延迟毫秒数,延迟期内不渲染骨架,从而平滑跳过这种瞬态:

<template> <div style="width: 240px"> <p> <label style="margin-right: 16px;">Switch Loading</label> <el-switch v-model="loading" /> </p> <el-skeleton style="width: 240px" :loading="loading" animated :throttle="500" > <template slot="template"> <el-skeleton-item variant="image" style="width: 240px; height: 240px;" /> <div style="padding: 14px;"> <el-skeleton-item variant="h3" style="width: 50%;" /> <div style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;" > <el-skeleton-item variant="text" style="margin-right: 16px;" /> <el-skeleton-item variant="text" style="width: 30%;" /> </div> </div> </template> <template> <el-card :body-style="{ padding: '0px', marginBottom: '1px'}"> <img src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png" class="image" /> <div style="padding: 14px;"> <span>Delicious hamberger</span> <div class="bottom card-header"> <span class="time">{{ currentDate }}</span> <el-button type="text" class="button">operation button</el-button> </div> </div> </el-card> </template> </el-skeleton> </div> </template> <script> export default { data() { return { loading: false, currentDate: '2021-06-01' } }, } </script>

从源码看,throttle并不是简单地对loading做节流,而是通过内部状态uiLoading实现「进入加载态延迟生效」的策略(index.vue):

watch: { loading: { handler(loading) { if (this.throttle <= 0) { this.uiLoading = loading; return; } if (loading) { clearTimeout(this.timeoutHandle); this.timeoutHandle = setTimeout(() => { this.uiLoading = this.loading; }, this.throttle); } else { this.uiLoading = loading; } }, immediate: true } }, data() { return { uiLoading: this.throttle <= 0 ? this.loading : false }; }

其行为可以精确概括为:

  1. 初始状态:若throttle > 0uiLoading强制为false,即使loadingtrue也暂不渲染骨架;
  2. loading变为true(进入加载)时:不立即切换,而是启动setTimeout,等待throttle毫秒后再把uiLoading同步为true并渲染骨架;
  3. loading变为false(加载结束)时:立即把uiLoading置为false,马上展示真实内容;
  4. 若在延迟窗口内loading又变回falseclearTimeout会取消尚未触发的骨架渲染——这正是「接口太快时完全不闪骨架」的实现基础。

因此,throttle的语义是「骨架进入延迟」而非「内容显示延迟」:加载结束后内容永远即时呈现,只有加载过程短于throttle阈值时骨架才被跳过,从而彻底规避闪烁。

API 速查:属性、插槽与类型定义

el-skeleton 属性(Skeleton Attributes)

属性说明类型可选值默认值
animated是否显示加载动画booleantrue / falsefalse
count渲染多少组骨架模板到 DOMnumber整数1
loading是否显示骨架屏booleantrue / falsetrue
rows段落行数,仅在未提供 template 插槽时生效number整数4
throttle骨架渲染延迟(毫秒)number整数0

以上属性与 packages/skeleton/src/index.vue 的props声明一一对应,同时已在 TypeScript 类型文件 types/skeleton.d.ts 中完整声明(其中rows的声明类型为 boolean,属于类型文件与源码实现不一致的历史遗留,实际运行时按 number 处理,引用时需注意)。

el-skeleton-item 属性(Skeleton Item Attributes)

属性说明类型可选值默认值
variant当前渲染的骨架单元类型Enum(string)p / h1 / h3 / text / caption / button / image / circle / recttext

插槽(Skeleton Slots)

插槽名说明
default真实渲染的 DOM(加载完成后的实际内容)
template自定义骨架模板(加载过程中的占位结构)

插槽的语义同样体现在模板逻辑中:当uiLoading为真时渲染template插槽(未提供时退化为内置段落),为假时渲染default插槽,两个插槽互斥切换。类型层面可参考 types/skeleton.d.ts 中的ElSkeletonSlots接口定义。

结语:一套组合拳打造无跳动的加载体验

综合来看,Element UI 的骨架屏组件设计了一套完整的加载体验方案:用variant拼装贴近真实 DOM 的自定义骨架,用animated提供轻柔的加载反馈,用count覆盖列表场景,再用loading+default插槽完成骨架与真实内容的无缝切换,最后以throttle从源码层面规避快速响应下的闪烁问题。遵循「骨架结构尽量贴近真实 DOM」与「count 尽量小」两条官方建议,配合 packages/skeleton/src/index.vue、packages/theme-chalk/src/skeleton-item.scss 等实现细节的理解,你就能在自己的项目中落地一套专业、流畅且性能友好的骨架屏方案。

【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 19:17:01

室内定位实战:Arduino+BLE4.0实现RSSI测距与三边定位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 19:16:35

工业无人机电力巡检Word方案自动化:POI-TL模板引擎实战指南

简介&#xff1a;这是一份Word格式的工业级无人机电力行业应用通用方案&#xff0c;共36页&#xff0c;面向电力行业项目决策者、无人机应用方案工程师及低空经济研究人员&#xff0c;针对传统人工巡检效率低、高危环境作业风险大、电网规模扩张后运维压力上升等现实痛点&#…

作者头像 李华
网站建设 2026/9/18 19:14:41

远程桌面0x204报错修复:CredSSP加密Oracle修正完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 19:14:39

SQL Server 2019安装深度指南:避坑、配置与生产就绪

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 19:13:38

卷积核不是滤镜:从视觉细胞到工程实践的深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华