gpui-kit Skeleton 骨架屏组件指南:加载占位、脉冲动画与主题定制
【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit
Skeleton(骨架屏)是 gpui-kit 提供的内容加载占位组件:在真实数据尚未就绪时,用带动画的占位块替代最终内容,既向用户传达"内容正在加载"的反馈,又通过占据与真实内容相近的版面来保持布局稳定。本指南以中文文档为骨架,结合 skeleton.rs 源码实现,系统讲解 Skeleton 的导入方式、各类占位形态的搭建方法、内置脉冲动画的底层原理、尺寸控制技巧与主题定制方案。读完本文,你将能够在 gpui-kit 应用中为列表、卡片、表格、表单等场景快速搭建专业的加载态界面。
组件定位与设计理念
Skeleton 在 gpui-kit 组件库中位于 crates/component/src/skeleton.rs,并在 crates/component/src/lib.rs 中以pub mod skeleton;公开导出。从源码结构看,它不是一个独立渲染的复杂控件,而是基于 GPUI 的IntoElement派生宏构建的轻量元素:
#[derive(IntoElement)] pub struct Skeleton { style: StyleRefinement, secondary: bool, }该结构只有两个字段:style(样式细化,可通过 Styled trait 链式叠加尺寸、圆角等)与secondary(是否使用更柔和的次级颜色)。这种"极简结构 + 完全交由样式工具定制外观"的设计,决定了 Skeleton 的使用哲学:组件本身不预设形状枚举,占位形态完全由 gpui 的尺寸与圆角工具组合出来。
导入
在 Rust 代码中按如下方式导入 Skeleton:
use gpui_kit::component::skeleton::Skeleton;同时,由于骨架屏大量依赖 gpui 的布局与样式工具(v_flex、h_flex、px、Styledtrait 等),实际使用时通常还需要引入:
use gpui::{px, Styled}; // 像素尺寸与样式链式方法 use gpui_kit::component::v_flex; // 或 h_flex,用于纵向/横向布局基础用法
最简单的骨架屏只有一个块:
Skeleton::new()在RenderOnce::render实现(skeleton.rs)中,它会被渲染为一个带内置动画的div,默认占满整行宽度(w_full)且高度为 4(h_4,16px),背景色取自主题的skeleton颜色。也就是说,单写一个Skeleton::new()即可得到一条通栏的加载占位条,随后可通过样式链覆盖宽高与圆角。
常用占位形态
文本行占位
模拟单行或多行文本加载,利用h_4/h_3控制行高、w(px(...))控制行宽:
// 单行文本 Skeleton::new() .w(px(250.)) .h_4() .rounded_md() // 多行文本(宽度递减模拟自然换行) v_flex() .gap_2() .child(Skeleton::new().w(px(250.)).h_4().rounded_md()) .child(Skeleton::new().w(px(200.)).h_4().rounded_md()) .child(Skeleton::new().w(px(180.)).h_4().rounded_md())圆形占位
头像、图标等圆形区域用rounded_full实现。size_12是 48×48px 的正方形再裁圆角:
// 头像占位 Skeleton::new() .size_12() .rounded_full() // 自定义尺寸的头像占位 Skeleton::new() .w(px(64.)) .h(px(64.)) .rounded_full()矩形占位
卡片封面、按钮等矩形区域直接指定宽高:
// 卡片图片占位 Skeleton::new() .w(px(250.)) .h(px(125.)) .rounded_md() // 按钮占位 Skeleton::new() .w(px(120.)) .h(px(40.)) .rounded_md()不同形状组合
骨架屏可以覆盖几乎所有版面元素,常见组合如下:
Skeleton::new().w(px(200.)).h_4().rounded_sm() // 紧凑文本 Skeleton::new().size_20().rounded_md() // 方形图片 Skeleton::new().w_full().h(px(200.)).rounded_lg() // 通栏横幅 Skeleton::new().size_6().rounded_md() // 小图标Secondary 变体
当占位需要比主骨架更弱的存在感(例如列表里的次要行)时,调用.secondary():
Skeleton::new() .secondary() .w(px(200.)) .h_4() .rounded_md()该方法的源码实现非常直接(skeleton.rs):
pub fn secondary(mut self) -> Self { self.secondary = true; self }而在渲染时(skeleton.rs),次级变体会对主题骨架色应用50% 透明度:
.bg(if self.secondary { cx.theme().skeleton.opacity(0.5).into() } else { cx.theme().skeleton })注意这里用的是 HSLA 的opacity直接调制,因此无论主题色是浅是深,次级变体都能得到一致的柔和观感。
内置脉冲动画
Skeleton 自带脉冲(pulse)动画,无需任何额外配置。源码中的动画定义(skeleton.rs)如下:
.with_animation( "skeleton", Animation::new(Duration::from_secs(2)) .repeat() .with_easing(bounce(ease_in_out)), move |this, delta| { let v = 1.0 - delta * 0.5; this.opacity(v) }, )对照中文文档的动画行为描述,可以逐一在源码中印证:
| 文档描述 | 源码实现 |
|---|---|
| 持续循环播放,周期 2 秒 | Animation::new(Duration::from_secs(2))配合.repeat() |
| 使用 bounce easing,并带有 ease-in-out 变化 | .with_easing(bounce(ease_in_out)) |
| 透明度在 100% 和 50% 之间往返 | 回调v = 1.0 - delta * 0.5,delta从 0 到 1 时v从 1.0 降到 0.5,循环往复 |
| 自动重复 | .repeat() |
该动画不可关闭:它不暴露开关参数,因为"闪烁的占位"本身就是加载状态最重要的视觉提示,关闭后用户将无法区分"空白"与"加载中"。这一点与中文文档的说明完全一致。
值得注意的细节是:动画名固定为"skeleton",它通过 GPUI 的AnimationExt与元素的opacity样式绑定;透明度以 0.5 为下限,保证占位块始终清晰可见,不会完全消失造成闪烁断层。
尺寸控制
Skeleton没有预设的尺寸枚举,这正是它与许多 UI 库骨架屏组件的关键区别。占位形态完全交给 gpui 的尺寸工具,常用的组合如下:
// 高度工具(间距基数 × 4px):h_3=12px, h_4=16px, h_5=20px, h_6=24px Skeleton::new().h_3() Skeleton::new().h_4() Skeleton::new().h_5() Skeleton::new().h_6() // 宽度工具 Skeleton::new().w(px(100.)) // 固定 100px Skeleton::new().w(px(200.)) // 固定 200px Skeleton::new().w_full() // 通栏 Skeleton::new().w_1_2() // 父容器 50% 宽 // 正方形尺寸:size_4=16px, size_8=32px, size_12=48px, size_16=64px Skeleton::new().size_4() Skeleton::new().size_8() Skeleton::new().size_12() Skeleton::new().size_16()配合rounded_sm/md/lg/full圆角工具,即可精确复刻目标内容的几何形状。
实战示例
以下示例均为加载态的完整布局骨架,可直接组合进真实页面。
资料卡片加载中
模拟"头像 + 姓名/邮箱 + 两行简介"的 profile 卡片:
v_flex() .gap_4() .p_4() .border_1() .border_color(cx.theme().border) .rounded(cx.theme().radius_lg) .child( h_flex() .gap_3() .items_center() .child(Skeleton::new().size_12().rounded_full()) // 头像 .child( v_flex() .gap_2() .child(Skeleton::new().w(px(120.)).h_4().rounded_md()) // 姓名 .child(Skeleton::new().w(px(100.)).h_3().rounded_md()) // 邮箱 ) ) .child( v_flex() .gap_2() .child(Skeleton::new().w_full().h_4().rounded_md()) // 简介第一行 .child(Skeleton::new().w(px(200.)).h_4().rounded_md()) // 简介第二行 )这里使用了cx.theme().border与cx.theme().radius_lg(需配合ActiveThemetrait),让加载骨架与最终内容保持完全一致的边框与圆角。
文章列表加载中
用children+map批量生成 3 条"缩略图 + 标题/摘要/日期"的列表项:
v_flex() .gap_6() .children((0..3).map(|_| { h_flex() .gap_4() .child(Skeleton::new().w(px(120.)).h(px(80.)).rounded_md()) // 缩略图 .child( v_flex() .gap_2() .flex_1() .child(Skeleton::new().w_full().h_5().rounded_md()) // 标题 .child(Skeleton::new().w(px(300.)).h_4().rounded_md()) // 摘要行1 .child(Skeleton::new().w(px(250.)).h_4().rounded_md()) // 摘要行2 .child(Skeleton::new().w(px(100.)).h_3().rounded_md()) // 日期 ) }))表格行加载中
模拟 5 行表格数据,用border_b_1复刻行分隔线:
v_flex() .gap_2() .children((0..5).map(|_| { h_flex() .gap_4() .p_3() .border_b_1() .border_color(cx.theme().border) .child(Skeleton::new().size_8().rounded_full()) // 状态指示 .child(Skeleton::new().w(px(150.)).h_4().rounded_md()) // 名称 .child(Skeleton::new().w(px(200.)).h_4().rounded_md()) // 邮箱 .child(Skeleton::new().w(px(80.)).h_4().rounded_md()) // 角色 .child(Skeleton::new().w(px(60.)).h_4().rounded_md()) // 操作 }))按钮加载态
一组按钮(主按钮、次按钮、图标按钮)的占位:
h_flex() .gap_3() .child(Skeleton::new().w(px(80.)).h(px(36.)).rounded_md()) // 主按钮 .child(Skeleton::new().w(px(70.)).h(px(36.)).rounded_md()) // 次按钮 .child(Skeleton::new().size_9().rounded_md()) // 图标按钮表单字段加载态
标签 + 输入框、标签 + 多行文本域:
v_flex() .gap_4() .child( v_flex() .gap_1() .child(Skeleton::new().w(px(60.)).h_4().rounded_md()) // 标签 .child(Skeleton::new().w_full().h(px(40.)).rounded_md()) // 输入框 ) .child( v_flex() .gap_1() .child(Skeleton::new().w(px(80.)).h_4().rounded_md()) // 标签 .child(Skeleton::new().w_full().h(px(120.)).rounded_md()) // 文本域 )条件加载
在实际应用中,通常以布尔状态切换骨架屏与真实内容:
if loading { Skeleton::new().w(px(200.)).h_4().rounded_md() } else { div().child("Actual content here") }这也是骨架屏的核心价值:先渲染与目标内容同构的占位,数据到达后原位替换,避免布局跳动。
主题定制
Skeleton 的背景色由主题 tokenskeleton.background驱动。在 schema.rs 中,该 token 被定义为可选的SharedString:
/// Skeleton background color. #[serde(rename = "skeleton.background")] pub skeleton: Option<SharedString>,如果主题没有显式配置,会按 schema.rs 的 fallback 逻辑回退到tokens.secondary:
apply_background_color!(skeleton, fallback = tokens.secondary);这也是中文文档中"未配置时回退到 secondary"的源码依据。组件侧在 theme_color.rs 中持有解析后的skeleton: Hsla颜色,渲染时直接取用。
内置默认主题 default-theme.json 给出了明暗两套默认值:
| 模式 | 取值 |
|---|---|
| 浅色(light) | "skeleton.background": "#f5f5f5" |
| 深色(dark) | "skeleton.background": "#171717" |
自定义时,在主题 JSON 中覆盖该 token 即可:
{ "skeleton.background": "#e2e8f0" }两条主题相关的补充说明:
- 回退链:
skeleton.background缺失 → 使用secondary色;若希望全局联动,直接调整secondary相关 token 即可影响所有未显式配置 skeleton 的界面。 secondary(true)变体:对骨架色施加 50% 透明度,用于同一页面中需要弱化的次级占位(例如列表的次要行),视觉上比主骨架更含蓄。
仓库内的实际应用
Skeleton 不是孤立的演示组件,它已被组件库内部用于真实加载态。例如 crates/component/src/list/loading.rs 用它实现了ListItem的加载列表项——每个LoadingItem渲染两行骨架(主行h_5、次级行secondary()),并禁用交互:
ListItem::new("skeleton").disabled(true).child( v_flex() .gap_1p5() .overflow_hidden() .child(Skeleton::new().h_5().w_48().max_w_full()) .child(Skeleton::new().secondary().h_3().w_64().max_w_full()), )同样的模式也出现在表格加载态(table/loading.rs)。这印证了文档推荐的用法:用secondary()区分主次信息层级,用max_w_full防止超宽溢出,用overflow_hidden保证极端宽度下骨架不撑破容器——这些都是在真实组件中沉淀下来的实践细节。
小结
gpui-kit 的 Skeleton 组件用极简的 API 承载了完整的加载态设计能力:默认通栏条状占位、Styled驱动的任意形状塑造、secondary次级变体、2 秒 bounce 脉冲动画(不可关闭),以及经由skeleton.backgroundtoken 的完整主题定制。将其与列表、表格等数据型组件配合使用,即可在数据到达前为用户提供流畅、稳定、与最终界面同构的加载体验。
【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考