你好 ![]()
基本思路
目标是根据实际的 Discourse UI 生成一个骨架屏加载器,而不是依赖硬编码的骨架屏模板。
该构建器允许管理员在页面上选择实际元素,并将其转换为骨架屏区域。
例如:
.title
.avatar
.topic-excerpt
.btn
.category-breadcrumb
然后,该组件在运行时使用这些选择器来生成骨架屏。
骨架屏预览
有趣的是,管理员无需手动编写选择器。构建器会分析所选元素并生成多个候选选择器。
生成有用的选择器比预期的更困难
我遇到的第一个问题就是选择器生成。
一种天真的实现方式很容易产生类似这样的结果:
.container.list-container.--topic-list .row.full-width .contents ...
虽然在技术上有效,但对于可重用的骨架屏配置来说,它过于具体了。
在使用图标时,情况甚至可能更糟,因为生成的选择器可能包含诸如 SVG 相关类名等实现细节。
我真正想要的是更接近这样的东西:
.badge-category__name
或者:
.badge-category__wrapper .d-icon
而不是一个描述整个 DOM 路径的选择器。
因此,构建器现在会生成多个候选项,并根据以下因素对它们进行评分:
- 选择器深度
- 类名数量
- 重复匹配
- 与状态相关的类
- 技术性的 SVG/图标类
- 选择器是否仍能匹配所选元素
结果是一个推荐选择器列表,管理员可以从中选择或手动编辑。
隐藏元素
还有一个单独的拾取器,用于选择那些在显示骨架屏时应该完全消失的元素。
例如:
.alert.alert-info
事实证明,这对于公告横幅或临时通知等元素非常有用,这些元素在构建/测试期间存在,但不应影响骨架屏布局。
这里的一个有趣问题是,隐藏元素时不能留下空白空间。
因此,被排除的元素不仅仅被视为一个简单的 display: none 列表——几何计算也必须理解该元素不是最终布局的一部分。
骨架屏预览
导航
也许最大的挑战是导航。
期望的行为是:
点击
↓
立即显示骨架屏
↓
Discourse 更改路由
↓
目标 DOM 出现
↓
隐藏骨架屏
诱人的解决方案是深入挂钩到导航生命周期中,并等待 DOM 完全稳定。
事实证明,这是错误的做法。
在某个时候,骨架屏甚至在实际内容已经存在之后还会显示几秒钟。
教训很简单:
骨架屏不应该成为 DOM 就绪的门控。
一旦目标页面拥有足够的实际内容来接管,骨架屏就应该让路。
这对导航的感知速度产生了巨大的影响。
视口
Discourse 已经拥有一个响应式视口系统,因此该组件现在使用相同的断点抽象:
xs
sm
md
lg
xl
2xl
骨架屏配置还可以按以下方式分组:
mobile → xs / sm
tablet → md
desktop → lg / xl / 2xl
all → everything
这意味着组件完全不需要知道实际的像素值。
如果 Discourse 更改了断点值,骨架屏组件就不必围绕新的硬编码数字进行重写。
几何缓存
选择器告诉我们应该渲染什么,但它们并不告诉我们骨架屏形状应该出现在哪里。
为此,我添加了几何捕获功能。
构建器可以测量实际渲染的区域并存储它们的几何信息,以便加载器可以在 SPA 导航期间立即渲染目标骨架屏。
还有一个明确的几何锁定选项,用于我不希望后续访问持续更改参考几何信息的场景。
这是另一个重要的区别:
选择器定义和渲染几何是两件不同的事情。
草稿
另一件变得必要的事情是草稿状态。
我不希望出现这种工作流程:
打开构建器
→ 花费 10 分钟进行配置
→ 关闭构建器
→ 一切都消失了
因此,构建器将进行中的草稿与实际的设置分开保存。
草稿的范围限定为页面/路由/视口组合,因此例如:
topic-list / lg
topic-list / md
topic-list / xs
不会意外地相互覆盖。
关闭构建器不会销毁所做的工作。
撤销
一旦构建器变得更加交互式,撤销系统就变得几乎不可避免了。
构建器存储其配置状态的快照:
{
"regions": \[\],
"excludes": \[\]
}
而不是试图维护 DOM 操作的历史记录。
这使得撤销系统更容易理解,并且使其独立于实际的页面 DOM。
该项目正在积极开发中。希望很快能为主题组件做好准备! ![]()




