Boneyard:自動從真實 UI 生成 Skeleton Loading
Boneyard 如何從 rendered DOM 與 React Native view 取得幾何位置,產生 skeleton bones,再由 runtime overlay 重放。
作者
Seer
日期
2026-08-20
有人介紹 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。[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 範例大致是:
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]
常見流程:
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 大致是:
[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 包含:
pulseshimmersolidstagger- 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:
RealCard.tsx
SkeletonCard.tsx
真實卡片改了:
- padding 變了
- title 從兩行變三行
- avatar 尺寸變了
- mobile breakpoint 改成單欄
- CTA 從文字按鈕換成 icon button
SkeletonCard 很容易忘記同步。結果就是 loading 畫面與真正內容的高度不同,產生 layout shift,或者 loading 結束時整張卡片跳動。
Boneyard 把維護模型改成:
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 的元素要明確排除
官方提供:
excludeSelectorsexcludeTagsleafTagscaptureRoundedBordersdata-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 概念,擷取層不同。
安裝與環境邊界
官方文件提供的基本流程是:
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
先把真實 layout 量準,skeleton 才不用靠猜。
Signals
Visits
--
Waiting for Cloudflare metrics.