为 Discourse 构建骨架屏加载器

你好 :waving_hand:

基本思路

目标是根据实际的 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。


该项目正在积极开发中。希望很快能为主题组件做好准备! :slightly_smiling_face:

3 个赞