---
slug: boneyard-skeleton-loading
title: Boneyard：自動從真實 UI 生成 Skeleton Loading
status: published
excerpt: Boneyard 如何從 rendered DOM 與 React Native view 取得幾何位置，產生 skeleton bones，再由 runtime overlay 重放。
category: Frontend
tags: [frontend, react, skeleton-loading, ui]
author: Seer
author_role: Author
read_time: 10 min
cover: "/static/boneyard-skeleton-loading-cover.png"
closing_note: "先把真實 layout 量準，skeleton 才不用靠猜。"
published_at: "2026-08-20T00:00:00Z"
updated_at: "2026-08-20T13:47:03Z"
---

有人介紹 Boneyard 時，常用一句很吸引人的話：

> 不用自己量佈局、不用手刻動畫。它直接讀你現有的 UI，提取結構，像素級生成 skeleton loading screen。

我查完官方 repo、官方文件、release、npm metadata 與實作後，結論是：**Boneyard 確實解決了 skeleton loading 最煩的維護問題，但「像素級」要縮小理解。它做的是瀏覽器 render 後的 DOM 幾何快照與矩形骨架重放，不是讀 screenshot 後理解整個 UI，也不是自動替你重新設計 loading state。**

它的價值很實際：把「真實 UI 與 skeleton 要維護兩份 layout」改成「真實 component 外包一層 `<Skeleton>`，再由 CLI 產生 `.bones.json`」。這讓 skeleton 可以跟著目前 render 出來的 UI 重新 capture。官方 repo 是 [0xGF/boneyard](https://github.com/0xGF/boneyard)。[1][2]

## 先講結論：它到底有多神

| 宣傳說法 | 查核結果 | 精確解讀 |
| --- | --- | --- |
| 自動生成 skeleton | **成立，但需要包裝 component 與跑 build** | 你要在真實 UI 外包 `<Skeleton name="...">`，CLI 再擷取它 |
| 直接讀現有 UI | **成立，但來源是 rendered DOM／React Native view** | Web 走 headless browser 與 DOM，React Native 走 fiber tree、UIManager measurement |
| 不用量佈局 | **大致成立** | 位置與尺寸由 browser 的 `getBoundingClientRect()` 取得，仍要處理 fixture、exclude 與 capture 條件 |
| 像素級 | **只對幾何快照成立** | 產物是 positioned rectangles，不是逐像素影像重建，也不會複製真實內容、陰影、圖示語意或互動狀態 |
| 不用手刻動畫 | **成立於基本動畫** | runtime 內建 `pulse`、`shimmer`、`solid`，也有 stagger 與 transition |
| 永遠跟 UI 同步 | **不成立** | layout 改變後需要重新執行 build，watch mode 或 Vite plugin 能降低手動維護成本 |
| 任何 UI 都能自動處理 | **過度宣傳** | grid、absolute、fixed、動態資料、登入保護頁、特殊 widget 仍有條件與 fallback |

這個工具值得用，但它的神奇之處在於**把幾何量測與 skeleton 維護自動化**，不在於它擁有理解 UI 的 AI。

## Boneyard 實際是什麼

Boneyard 是一個 TypeScript skeleton loading framework，npm package 名稱為 `boneyard-js`。查核時 package latest metadata 顯示版本 `1.9.0`，license 為 MIT，runtime dependency 包含 Playwright。repo 的 `main` 分支在本輪固定查核到 commit `c085b32fe5647ac055871282ec93fa622149d183`，對應 GitHub `v1.9.0` release。[1][7][8]

官方列出的 adapter 包含：

- React
- Preact
- Vue
- Svelte 5
- Angular
- React Native
- Vite plugin

不同框架最後都輸出同一種 `.bones.json` 結構。[2] Web 端由 CLI 或 Vite plugin 開 headless browser，React Native 則由 component 在開發模式掃描 view，再把 bone data 傳給 CLI。[5]

## 它的實際工作流程

### 1. 先在 component 外包 Skeleton

React 範例大致是：

```tsx
import { Skeleton } from 'boneyard-js/react'

function BlogPage() {
  const { data, isLoading } = useFetch('/api/post')

  return (
    <Skeleton name="blog-card" loading={isLoading}>
      {data && <BlogCard data={data} />}
    </Skeleton>
  )
}
```

`name` 是後續產生 bones 檔案與 registry lookup 的識別名稱。Boneyard 不是拿一個完全沒有接觸點的既有網站，自己猜出哪些 component 要有 skeleton。你仍然要把 loading 邊界標出來。[2][10][11]

### 2. CLI 開 dev server 並擷取指定 breakpoint

官方預設會在 `375`、`768`、`1280` 等 viewport 寬度 capture。CLI 會找出頁面上的 `[data-boneyard]` 元素，讀取每個 Skeleton 的 config，再呼叫 snapshot function，將結果寫成每個 component 對應的 `.bones.json`。[2][3][4]

常見流程：

```bash
npm install boneyard-js
npx boneyard-js build
```

第一次執行需要讓 CLI 能存取 dev server。官方文件也提醒，因為 capture 使用 Playwright，第一次可能需要額外安裝 Chromium browser。[11]

### 3. DOM walker 取得真正的幾何位置

核心 `snapshotBones()` 不是用猜的，它會：

- 讀 root container 的 `getBoundingClientRect()`
- 遞迴走訪可見 DOM 子元素
- 忽略 `display: none`、`visibility: hidden`、`opacity: 0`
- 將 `img`、`svg`、`video`、`canvas`、表單元素視為 leaf
- 將 `p`、標題、`li`、table cell 等預設視為 leaf
- 對每個 leaf 讀取 bounding rect
- 將 x 與 width 轉成相對 root 的百分比
- 將 y、height、border radius 保存成 bone data
- 對有背景、背景圖或圓角 border 的 container 另外記錄 container bone

這些行為在 `extract.ts` 的實作中可以直接看到。它的註解寫的是 snapshot rendered DOM 的 visual layout，核心座標來源是瀏覽器實際排版後的 rect。[4]

產生的單一 bone 大致是：

```text
[x, y, width%, height, borderRadius, isContainer?]
```

因此「像素級」比較準確的說法是：**在同一個 capture 條件下，骨架矩形的位置與尺寸來自實際 UI 的 browser geometry。**[3][4][6]

### 4. runtime 把骨架畫回同一個 container

runtime 會把真實 content 隱藏，放上一層 absolute overlay，再依照 `.bones.json` 裡的 x、y、width、height、radius 畫出矩形。`c: true` 的 container bone 會被跳過，避免父層半透明背景與子 bone 疊加後變得過暗。[3][5]

內建 animation 包含：

- `pulse`
- `shimmer`
- `solid`
- `stagger`
- loading 結束時的 `transition`

`pulse` 與 `shimmer` 都是在 runtime 由 CSS animation 套上去，不需要為每個 component 手刻一套 loading animation。[5]

## 「像素級」到底準不準

### 它做得很準的地方

**位置與尺寸。**

Boneyard 不是用一組手寫的 `SkeletonText width="80%"` 來猜。它在真實 render 後直接讀 element rect，所以 card 寬度、avatar 位置、段落高度、圖片比例、按鈕尺寸與 breakpoint 變化，都能從當下的 UI layout 取得。[4]

**Responsive breakpoint。**

CLI 可以在多個 viewport capture，runtime 會依目前 container width 選擇合適的 breakpoint。官方文件說明，產生的 bones 會以 breakpoint map 保存，component 會選最近的匹配寬度。[2][6][11]

**文字造成的高度。**

DOM snapshot 路徑會保留實際文字元素的 rect。另一條 `fromElement()`／compiled layout 路徑則會保存文字、font、line-height，利用 `@chenglou/pretext` 進行文字排版推算。這能支援沒有 DOM 的 compiled layout 場景，但它仍然是矩形 placeholder，不會顯示真正文字。[4][6]

**圖片與圓角。**

`img`、`video`、`canvas` 被視為 leaf，會依實際寬高或 aspect ratio 產生 bone。近似正方形的 media 會自動判成圓形候選，border radius 也會從 computed style 取出。這對頭像、卡片圖與圓形 icon 很有用。[4]

### 它沒有做到的地方

**它沒有理解視覺內容。**

它知道某個元素在 x、y、width、height 的位置，卻不會知道圖片裡是人、商品或圖表，也不會判斷這個元件在產品語意上應該用哪一種 loading representation。

**它不會產生完整的 DOM skeleton。**

輸出重點是 bone rectangles 與 runtime overlay。你不會得到一份獨立、可任意編輯的 HTML skeleton tree。這種設計讓產物小、runtime 簡單，也代表自訂語意與交互狀態仍由原本 component 負責。

**它不會複製所有 CSS 視覺效果。**

文件與 source 主要處理位置、尺寸、背景、border radius、文字幾何與 container。陰影、複雜 mask、SVG 內部結構、第三方 canvas widget、sticky／fixed layer 的完整語意，不應直接等同於像素級複製。[4][6]

**capture 條件會影響結果。**

如果資料還沒載入，capture 到的可能是空容器。如果畫面需要登入，CLI 可能被 redirect。如果 component 依賴 API 結果才有完整 layout，就要用 `fixture` 提供 build-time mock content，或設定 web-only auth cookies／headers。[10][11]

## Boneyard 解決的是哪個痛點

傳統 skeleton loading 通常有兩份 markup：

```text
RealCard.tsx
SkeletonCard.tsx
```

真實卡片改了：

- padding 變了
- title 從兩行變三行
- avatar 尺寸變了
- mobile breakpoint 改成單欄
- CTA 從文字按鈕換成 icon button

SkeletonCard 很容易忘記同步。結果就是 loading 畫面與真正內容的高度不同，產生 layout shift，或者 loading 結束時整張卡片跳動。

Boneyard 把維護模型改成：

```text
RealCard.tsx
  → <Skeleton name="card">
  → CLI／Vite plugin capture
  → card.bones.json
  → runtime overlay
```

這個設計的好處是，skeleton 的幾何來源和真實 component 綁在一起。官方首頁也把重點放在 static JSON、約 7.5KB runtime、incremental build，以及只重新 capture 變動 component。[9]

## 但它仍然需要工程上的契約

### 你還是要標出 Skeleton 邊界

Boneyard 要找的是命名的 `<Skeleton>`。它不會自動把整個任意網站拆成合適的 loading component。這是一個重要邊界：**它自動化的是已標記 component 的幾何擷取與重放。**[2][3]

### 動態資料要準備 fixture

官方 install 文件明確說明，若 component 需要 API data、位於 authentication 後方，或 build 時拿不到真實資料，可以用 `fixture` 提供 capture 時要 render 的 mock content。fixture 只在 build 使用，不會進 production。[10][11]

這個設計很好，因為 skeleton 的目標是固定 layout，capture 不應該依賴某次 API 是否剛好回來。但 fixture 自己也要維護。若 fixture 的文字長度、圖片比例或資料筆數與正式頁面差太多，產出的骨架仍可能偏離實際 loading 狀態。

### 不想出現在 skeleton 的元素要明確排除

官方提供：

- `excludeSelectors`
- `excludeTags`
- `leafTags`
- `captureRoundedBorders`
- `data-no-skeleton`

例如 live chart、nav、footer、aside、裝飾 icon 不一定適合變成骨架。Boneyard 可以在 capture 時略過它們，但你要明確提供規則。[6][10]

### layout 改了，還是要重新 capture

官方文件的流程是 layout 變更後重新執行 `npx boneyard-js build`。Vite plugin 可以在 dev server 啟動與 HMR 時自動重抓，CLI 也有 `--watch`。這降低了同步成本，沒有把同步成本變成零。[2][10][11]

### Grid、absolute 與 fixed 需要保守看待

`fromElement()` 的 compiled layout source 對 `grid`、`inline-grid`、`absolute`、`fixed` 會採固定尺寸 leaf snapshot，因為這些 layout 不容易由簡化的 layout engine 重算。[4]

這代表：

- DOM snapshot 路徑能取得當下 browser geometry
- compiled layout 路徑可以在沒有 DOM 時重算部分 layout
- 複雜 CSS layout 不代表能被完整轉成跨寬度的語意 layout

需要精確檢查的 UI 包含：

- CSS grid dashboard
- 依內容高度變化的表格
- sticky header 與 fixed bottom bar
- portal、modal、tooltip
- canvas／chart／地圖
- virtualized list
- 依權限或 feature flag 改變結構的 component

## React Native 版本是另一條路徑

React Native 不走 browser DOM。官方 README 與 release notes 描述的是 dev mode auto-scan，透過 React fiber tree 與 `UIManager` 量測 views，再把 bone data 傳到 CLI。`--native` 會從 device／simulator capture，輸出同一類型的 bones data。[2][3][7]

所以不能把 Web 的「DOM snapshot」直接套到 React Native。兩者共享輸出格式與 runtime 概念，擷取層不同。

## 安裝與環境邊界

官方文件提供的基本流程是：

```bash
npm install boneyard-js
npx boneyard-js build
```

實務上還要注意：

- 需要 dev server 正常啟動
- CLI 需要能找到頁面與 `<Skeleton>`
- Playwright 可能需要 Chromium browser
- 認證頁面要用 fixture、dev bypass、cookies 或 headers
- Next.js App Router 使用 component hook 時要處理 `use client`
- React Native 要有可被 CLI 掃描的 device／simulator
- layout 變更後要重新生成 bones

npm metadata 顯示 `playwright` 是 runtime dependency，peer dependencies 則包含 React、Vue、Svelte、Preact、Angular、React Native 與 Vite。[8]

本輪只讀官方 source、README、docs、release 與 npm metadata，沒有安裝 package、沒有啟動 dev server、沒有跑 capture，也沒有驗證任何實際頁面的 pixel diff。

## 我會怎麼評價它

### 適合

- React／Next.js／Vite 等 component UI
- 卡片、列表、profile、dashboard、商品 grid
- 真實 layout 經常調整，skeleton 容易失同步的專案
- 想保留 skeleton 動畫，但不想每個 component 手刻骨架尺寸
- 已經有 dev server 與可以穩定 capture 的 fixture

### 需要先做小型 proof of concept

- 登入後才有資料的頁面
- 複雜 grid 與 responsive dashboard
- virtualized list
- chart、canvas、地圖與第三方 widget
- portal、modal、sticky、fixed layer
- 多種 feature flag 會改變 DOM 結構的頁面
- React Native 需要同時支援多種 device width 與 dynamic type 的 app

### 不要把它當成

- screenshot-to-code 工具
- AI UI understanding agent
- 完整 visual regression system
- 自動替你設計 skeleton 資訊層級的工具
- 不需要 fixture、測試資料與重新 capture 的零維護方案

## 最終判斷

Boneyard **確實比手刻 skeleton 更聰明，也有清楚的工程價值**。它把最容易漂移的那一段交給真實 browser layout 來量測，並用靜態 bones JSON 在 runtime 重放。[4] 對一般 component-based UI，這能大幅減少 skeleton markup 的重複與同步負擔。[5]

「像素級」需要改成更精確的說法：

> Boneyard 會從真實 render 的 DOM 幾何位置擷取矩形骨架，讓 skeleton 在 capture 的 viewport 與資料狀態下貼近原始 layout。它不是逐像素複製畫面，也不會自動理解所有 CSS、資料狀態與第三方 widget。

這個版本的結論比較可信，也比較能幫工程團隊做採用決策。

## 查核邊界

- 查核日：2026-08-20
- 查核 repo：`0xGF/boneyard`
- 查核 commit：`c085b32fe5647ac055871282ec93fa622149d183`
- 查核 release：`v1.9.0`
- npm package：`boneyard-js@1.9.0`
- License：MIT
- 本輪未安裝、未執行、未啟動 server、未做 pixel diff
- GitHub stars、forks、官方首頁 downloads 與 bones 數量屬於動態展示，不當成穩定產品指標

## Sources

[1] https://github.com/0xGF/boneyard — canonical repository
[2] https://raw.githubusercontent.com/0xGF/boneyard/c085b32fe5647ac055871282ec93fa622149d183/README.md — README at inspected commit
[3] https://raw.githubusercontent.com/0xGF/boneyard/c085b32fe5647ac055871282ec93fa622149d183/CLAUDE.md — project structure and implementation notes
[4] https://raw.githubusercontent.com/0xGF/boneyard/c085b32fe5647ac055871282ec93fa622149d183/packages/boneyard/src/extract.ts — DOM extraction implementation
[5] https://raw.githubusercontent.com/0xGF/boneyard/c085b32fe5647ac055871282ec93fa622149d183/packages/boneyard/src/react.tsx — React runtime renderer
[6] https://raw.githubusercontent.com/0xGF/boneyard/c085b32fe5647ac055871282ec93fa622149d183/packages/boneyard/src/types.ts — snapshot types and config
[7] https://github.com/0xGF/boneyard/releases/tag/v1.9.0 — v1.9.0 release
[8] https://registry.npmjs.org/boneyard-js/latest — npm latest metadata
[9] https://boneyard.vercel.app — official product homepage
[10] https://boneyard.vercel.app/features — official feature docs
[11] https://boneyard.vercel.app/install — official install docs
