# 为 Discourse 构建骨架屏加载器

**URL:** https://meta.discourse.org/t/building-a-skeleton-layout-loader-for-discourse/410338
**Category:** Development
**Created:** [2026年八月18日 14:46 UTC](https://meta.discourse.org/t/building-a-skeleton-layout-loader-for-discourse/410338 "2026-08-18T14:46:57Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![Don](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/don/32/228726_2.png) [@Don](https://meta.discourse.org/u/Don)
#### Post date: [2026年八月18日 14:46 UTC](https://meta.discourse.org/t/building-a-skeleton-layout-loader-for-discourse/410338/1 "2026-08-18T14:46:57Z")

</div>

你好 👋

## 基本思路

目标是根据 **实际的 Discourse UI 生成** 一个骨架屏加载器，而不是依赖硬编码的骨架屏模板。

该构建器允许管理员在页面上选择实际元素，并将其转换为骨架屏区域。

例如：

```css
.title
.avatar
.topic-excerpt
.btn
.category-breadcrumb

```

然后，该组件在运行时使用这些选择器来生成骨架屏。

 ![Screenshot 2026-08-18 at 15.55.49](https://global.discourse-cdn.com/meta/original/4X/8/1/d/81d150eeeb32490dfbe9e8b181ab7b40d73a40ad.png)

骨架屏预览

 ![Screenshot 2026-08-18 at 15.56.12](https://global.discourse-cdn.com/meta/original/4X/3/8/7/38794c7d071e123ff658fac9870dd64566dceefe.png)

有趣的是，管理员无需手动编写选择器。构建器会分析所选元素并生成多个候选选择器。

* * *

## 生成有用的选择器比预期的更困难

我遇到的第一个问题就是选择器生成。

一种天真的实现方式很容易产生类似这样的结果：

```css
.container.list-container.--topic-list .row.full-width .contents ...

```

虽然在技术上有效，但对于可重用的骨架屏配置来说，它过于具体了。

在使用图标时，情况甚至可能更糟，因为生成的选择器可能包含诸如 SVG 相关类名等实现细节。

我真正想要的是更接近这样的东西：

```css
.badge-category__name

```

或者：

```css
.badge-category__wrapper .d-icon

```

而不是一个描述整个 DOM 路径的选择器。

因此，构建器现在会生成多个候选项，并根据以下因素对它们进行评分：

- 选择器深度
- 类名数量
- 重复匹配
- 与状态相关的类
- 技术性的 SVG/图标类
- 选择器是否仍能匹配所选元素

结果是一个推荐选择器列表，管理员可以从中选择或手动编辑。

* * *

## 隐藏元素

还有一个单独的拾取器，用于选择那些在显示骨架屏时应该完全消失的元素。

例如：

```css
.alert.alert-info

```

事实证明，这对于公告横幅或临时通知等元素非常有用，这些元素在构建/测试期间存在，但不应影响骨架屏布局。

这里的一个有趣问题是，隐藏元素时不能留下空白空间。

因此，被排除的元素不仅仅被视为一个简单的 `display: none` 列表——几何计算也必须理解该元素不是最终布局的一部分。

 ![Screenshot 2026-08-18 at 16.05.16](https://global.discourse-cdn.com/meta/original/4X/7/e/2/7e2c7c83e7b49d59643d71dfb86c6d9a3243db52.png)

骨架屏预览

 ![Screenshot 2026-08-18 at 16.05.35](https://global.discourse-cdn.com/meta/original/4X/7/f/2/7f2b8b02ae3a75053d164f8d1a0de10f62b752ee.png)

* * *

## 导航

也许最大的挑战是导航。

期望的行为是：

```plaintext
点击

  ↓

立即显示骨架屏

  ↓

Discourse 更改路由

  ↓

目标 DOM 出现

  ↓

隐藏骨架屏

```

诱人的解决方案是深入挂钩到导航生命周期中，并等待 DOM 完全稳定。

事实证明，这是错误的做法。

在某个时候，骨架屏甚至在 **实际内容已经存在之后** 还会显示几秒钟。

教训很简单：

> 骨架屏不应该成为 DOM 就绪的门控。

一旦目标页面拥有足够的实际内容来接管，骨架屏就应该让路。

这对导航的感知速度产生了巨大的影响。

* * *

## 视口

Discourse 已经拥有一个响应式视口系统，因此该组件现在使用相同的断点抽象：

```plaintext
xs
sm
md
lg
xl
2xl

```

骨架屏配置还可以按以下方式分组：

```plaintext
mobile → xs / sm
tablet → md
desktop → lg / xl / 2xl
all → everything

```

这意味着组件完全不需要知道实际的像素值。

如果 Discourse 更改了断点值，骨架屏组件就不必围绕新的硬编码数字进行重写。

 ![Screenshot 2026-08-18 at 16.12.30](https://global.discourse-cdn.com/meta/original/4X/a/b/9/ab92204ad03ebee29441899e202e3801ad9aaa3a.png)

* * *

## 几何缓存

选择器告诉我们 **应该渲染什么** ，但它们并不告诉我们骨架屏形状应该出现在 **哪里** 。

为此，我添加了几何捕获功能。

构建器可以测量实际渲染的区域并存储它们的几何信息，以便加载器可以在 SPA 导航期间立即渲染目标骨架屏。

还有一个明确的几何锁定选项，用于我不希望后续访问持续更改参考几何信息的场景。

这是另一个重要的区别：

**选择器定义** 和 **渲染几何** 是两件不同的事情。

* * *

## 草稿

另一件变得必要的事情是草稿状态。

我不希望出现这种工作流程：

```plaintext
打开构建器

→ 花费 10 分钟进行配置

→ 关闭构建器

→ 一切都消失了

```

因此，构建器将进行中的草稿与实际的设置分开保存。

草稿的范围限定为页面/路由/视口组合，因此例如：

```plaintext
topic-list / lg
topic-list / md
topic-list / xs

```

不会意外地相互覆盖。

关闭构建器不会销毁所做的工作。

* * *

## 撤销

一旦构建器变得更加交互式，撤销系统就变得几乎不可避免了。

构建器存储其配置状态的快照：

```plaintext
{
  "regions": \[\],
  "excludes": \[\]
}

```

而不是试图维护 DOM 操作的历史记录。

这使得撤销系统更容易理解，并且使其独立于实际的页面 DOM。

* * *

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