070

Boneyard:自動從真實 UI 生成 Skeleton Loading

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 viewWeb 走 headless browser 與 DOM,React Native 走 fiber tree、UIManager measurement
不用量佈局大致成立位置與尺寸由 browser 的 getBoundingClientRect() 取得,仍要處理 fixture、exclude 與 capture 條件
像素級只對幾何快照成立產物是 positioned rectangles,不是逐像素影像重建,也不會複製真實內容、陰影、圖示語意或互動狀態
不用手刻動畫成立於基本動畫runtime 內建 pulseshimmersolid,也有 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

官方預設會在 3757681280 等 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: nonevisibility: hiddenopacity: 0
  • imgsvgvideocanvas、表單元素視為 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 包含:

  • pulse
  • shimmer
  • solid
  • stagger
  • loading 結束時的 transition

pulseshimmer 都是在 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]

圖片與圓角。

imgvideocanvas 被視為 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 的元素要明確排除

官方提供:

  • 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 對 gridinline-gridabsolutefixed 會採固定尺寸 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 才不用靠猜。

Visits

--

Waiting for Cloudflare metrics.