Liquid DOM

AndrewPrifer/liquid-dom · WebGPU liquid-glass · pnpm monorepo

1 — 概要

Liquid DOM は、WebGPU で iOS 風リキッドグラス UI を描画するモノレポ。命令型シーングラフ、React 19 バインディング、Three.js / React Three Fiber 統合、レンダラー非依存の SwiftUI 風レイアウトを、統合レイヤごとに分割した npm パッケージとして提供する。

5 公開パッケージ
WebGPU グラス描画 API
SDF 同一 Container 内 Glass 融合
DOM Html ノードを GPU テクスチャへ
パッケージ選び: 所有するレンダラーに合わせて最下位レイヤを選ぶ。React で宣言的に書きたい場合は @liquid-dom/react、Three WebGPU 上に合成する場合は @liquid-dom/three / @liquid-dom/r3f。レイアウトだけ必要なら @liquid-dom/layout は他パッケージに依存しない。
2 — パッケージ一覧
パッケージ 役割 使う場面
@liquid-dom/core 命令型シーングラフ、WebGPU Renderer、再利用可能な WebGPU コア、@liquid-dom/core/layout サブパス シーングラフを直接制御する、または別レンダラー向けアダプタを自作する
@liquid-dom/react React 19 バインディング(LiquidCanvas / LiquidScene) React でグラス UI を宣言し、canvas とフレームループを LiquidCanvas に任せる
@liquid-dom/three Three の WebGPU レンダラー内で liquid-glass コアをホスト Three シーンを backdrop として描画し、その上にグラスをポスト合成する
@liquid-dom/r3f @liquid-dom/three と @liquid-dom/react の R3F ブリッジ React Three Fiber + Three WebGPU で React グラス UI を重ねる
@liquid-dom/layout レンダラー非依存の SwiftUI 風レイアウトエンジン 測定・配置だけ必要で、WebGPU や React に依存したくない
%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#d4e8f4', 'primaryTextColor': '#1a2a35', 'primaryBorderColor': '#3a7a9a', 'lineColor': '#3a7a9a', 'secondaryColor': '#eef6fa', 'tertiaryColor': '#f5f0e0' }}}%%
flowchart TD
  layout["@liquid-dom/layout"]
  core["@liquid-dom/core"]
  react["@liquid-dom/react"]
  three["@liquid-dom/three"]
  r3f["@liquid-dom/r3f"]

  layout --> core
  core --> react
  core --> three
  react --> r3f
  three --> r3f
        

@liquid-dom/layout は独立。@liquid-dom/core が layout サブパスで利用。@liquid-dom/react は core の layout クラスを React コンポーネント化。@liquid-dom/three は Three WebGPU 内にコアをホスト。@liquid-dom/r3f が React と Three を結合する。

3 — アーキテクチャ
ノード 子として受け付ける型 要点
Scene Container, Html, Group ルート。兄弟は zIndex → 追加順で描画
Container Glass, Group 同一 Container 内の Glass は 1 つの SDF フィールドに融合し光学設定を共有
Glass Html, Group cornerRadius + cornerSmoothing で squircle 近似。子 Html をグラス越しにサンプリング
Html — ライブ DOM を GPU テクスチャへコピー。blur はレンダラー側の適応ブラー
Group / StackingContext 親に応じた子 Group は変換の合成。StackingContext は zIndex で子をグループ化

シーン直下の Html は、下位の Container の backdrop になる。より手前の Container より上にある Html は覆い、後続 Container の backdrop になる。

DOM / Html ホスト → HTML-in-Canvas コピー → GPU テクスチャ → 適応ブラー(任意) → SDF グラス合成 → WebGPU canvas

Adaptive blur

Html.blur > 0 のとき適応ブラーパイプライン。CSS filter ではなく DOM コピー後の GPU 処理。

Adaptive tint

Container の tint / blur / thickness で光学パラメータを一括制御。

Pointer events

Glass.pointerEvents でヒットテスト。命令型 API では typed リスナー、React では通常の DOM イベント。

Three 合成

Three シーンを RenderTarget に描画し、backdrop テクスチャとして ThreeGlassRenderer に渡す。

4 — 統合レイヤ
import { Container, Glass, Html, Renderer, Scene } from '@liquid-dom/core'

const scene = new Scene()
const container = new Container({ blur: 12, spacing: 28, thickness: 90 })
const glass = new Glass({ width: 280, height: 180, cornerRadius: 48, pointerEvents: true })
glass.add(new Html({ width: 180, height: 56, element: contentButton }))
container.add(glass)
scene.add(container)

const renderer = new Renderer({ scene })
document.body.append(renderer.canvas)
requestAnimationFrame(function frame() { renderer.render(); requestAnimationFrame(frame) })
import { Frame, Glass, GlassContainer, Html, LiquidCanvas } from '@liquid-dom/react'

export function App() {
  return (
    <LiquidCanvas style={{ width: '100vw', height: '100vh' }}>
      <GlassContainer blur={12} spacing={28}>
        <Frame width={280} height={160}>
          <Glass cornerRadius={44} pointerEvents>
            <Html sizing="fill"><button>Native content</button></Html>
          </Glass>
        </Frame>
      </GlassContainer>
    </LiquidCanvas>
  )
}

LiquidScene は canvas を作らない headless モード。Three アダプタなど別レンダラーが出力を所有するときに ref 経由で update(proposal, delta) を呼ぶ。

import * as THREE from 'three/webgpu'
import { Container, Glass, Scene } from '@liquid-dom/core'
import { ThreeGlassRenderer } from '@liquid-dom/three'

const renderer = new THREE.WebGPURenderer({ canvas })
await renderer.init()
const backdrop = new THREE.RenderTarget(1, 1, { colorSpace: THREE.SRGBColorSpace })
const glassRenderer = new ThreeGlassRenderer({ renderer, scene: glassScene })

function frame() {
  renderer.setRenderTarget(backdrop)
  renderer.render(threeScene, camera)
  renderer.setRenderTarget(null)
  glassRenderer.render({ backdrop, width, height })
  requestAnimationFrame(frame)
}
WebGLRenderer は不可。 Three 統合は three/webgpu の WebGPURenderer が前提。
5 — セットアップ
pnpm add @liquid-dom/core
pnpm add @liquid-dom/react react react-dom
pnpm add @liquid-dom/three @liquid-dom/core three
pnpm add @liquid-dom/r3f @liquid-dom/react @react-three/fiber react react-dom three
pnpm add @liquid-dom/layout

各パッケージ README に peer dependency の完全な一覧がある。React バインディングは React 19 を想定。

pnpm install
pnpm -r build
pnpm --filter @liquid-dom/layout test
pnpm --filter @liquid-dom/core test
pnpm --filter @liquid-dom/react test

# デモ(ルート package.json scripts)
pnpm dev              # core + editor
pnpm dev:minimal      # core + react + minimal demo
pnpm dev:three        # core + three + three-integration
pnpm dev:three-react  # + three-react-integration
pnpm dev:three-r3f    # + three-r3f-integration
6 — 実行要件
要件 対象 詳細
navigator.gpu グラス描画全体 WebGPU 対応ブラウザが必須。シーングラフ構築だけならブラウザ風環境でも可
HTML-in-Canvas Html ノード 実験 API。<canvas layoutsubtree> と canvas paint イベントで DOM をテクスチャ化
Chrome フラグ DOM バックド Html chrome://flags/#canvas-draw-element を有効化(2026-05 時点)
Three WebGPU @liquid-dom/three / r3f WebGLRenderer ではなく WebGPURenderer
仕様参照: DOM を canvas に描画する API は WICG HTML-in-Canvas を参照。