Liquid DOM

repository·master·Indexed 25 days ago

https://github.com/andrewprifer/liquid-dom

A monorepo providing WebGPU liquid-glass rendering, a renderer-agnostic layout engine, and integrations for React and Three.js/R3F. It features a DOM-backed renderer that captures live HTML content into GPU textures to achieve high-performance optical effects like blur, refraction, and squircle-shaped glass surfaces via an imperative scene graph.

Tokens
31.8K
Snippets
57
Records
169
Agent score
81%

What's inside liquid-dom

  1. Overview of @liquid-dom/core

    master

    The @liquid-dom/core package is the foundation of the Liquid DOM ecosystem. It provides:

    • Liquid-glass renderer: A DOM-backed renderer.
    • Imperative scene graph: For managing the hierarchy of visual elements.
    • WebGPU renderer: A renderer that owns and manages a WebGPU canvas.
    • WebGPU core: A reusable core designed for adapters.
    • Layout API: Accessible via @liquid-dom/core/layout.
  2. Choose the right Liquid DOM package for your project

    master

    Liquid DOM is organized into packages based on your rendering target and preferred programming model. Select the package that matches your existing stack:

    • Direct Control / Custom Renderer: Use @liquid-dom/core for an imperative DOM-backed scene graph, the WebGPU renderer, and layout classes.
    • React (Declarative): Use @liquid-dom/react to describe glass UI using React 19 components. This is ideal if you want LiquidCanvas to manage the canvas or if you need a headless scene.
    • Three.js (WebGPU): Use @liquid-dom/three to composite liquid glass over an existing Three.js WebGPU renderer.
    • React Three Fiber (R3F): Use @liquid-dom/r3f if you are using R3F with Three's WebGPU renderer and want React-based liquid-glass UI.
    • Layout Only: Use @liquid-dom/layout if you only need the renderer-agnostic, SwiftUI-style measurement and placement engine without any rendering dependency.
  3. How the @liquid-dom/r3f Component API works

    master

    The Component API provides a structured way to wrap your R3F and Liquid-Glass content using a hierarchy of components:

    • LiquidGlassR3F.Root: The top-level provider that shares the scene ref and invalidation bridge between the R3F canvas and the liquid-glass scene.
    • LiquidGlassR3F.Scene: Creates a headless LiquidScene specifically for @liquid-dom/react layout components.
    • LiquidGlassR3F.Render (or simply <LiquidGlassR3F />): Placed inside the R3F <Canvas>. It takes over the final rendering with a positive frame priority.

    Component Props Reference

    ComponentProps
    LiquidGlassR3F.Rootchildren, optional sceneRootRef
    LiquidGlassR3F.Scenechildren
    LiquidGlassR3F.RendersceneRootRef, renderPriority, enabled, dpr, outputTexture, renderTarget, onError
    LiquidGlassR3FSame props as LiquidGlassR3F.Render
    <LiquidGlassR3F.Root>
      <Canvas gl={createWebGpuRenderer}>
        <LiquidGlassR3F.Render renderPriority={1} />
      </Canvas>
    
      <LiquidGlassR3F.Scene>
        {/* @liquid-dom/react layout */}
      </LiquidGlassR3F.Scene>
    </LiquidGlassR3F.Root>
  4. Understand the Liquid DOM Scene Graph

    master

    The Liquid DOM scene graph is a hierarchical structure of nodes that defines how elements are composed, transformed, and rendered. The hierarchy follows specific relationship rules based on the type of parent node.

    Core Node Types

    • Scene: The root node. Accepts Container, Html, and Group children.
    • Container: Controls optical behavior (blur, refraction, etc.) for its children. Accepts Glass and Group children. Multiple Glass children in the same container are fused into a single SDF field.
    • Glass: Defines a shaped glass surface. Accepts Html and Group children. Each Html child is sampled through the glass.
    • Html: Wraps live DOM content. It owns a host element and can mount an HTMLElement into it.
    • Group: Composes transforms (position, scale, rotation) without affecting the stacking order.
    • StackingContext: Groups children under a shared zIndex.

    Relationship Rules

    Nodes must match the requirements of their nearest non-group parent. For example, a Glass node must eventually be nested under a Container; it cannot be a direct child of another Glass, even if wrapped in a Group or StackingContext.

    Transform Properties

    All transformable nodes expose:

    • x, y, origin (in CSS pixels)
    • scaleX, scaleY
    • rotation (in radians)
    import {
      Container,
      Glass,
      Group,
      Html,
      Scene,
      StackingContext,
    } from '@liquid-dom/core'
  5. Understand the Adaptive Blur Cost Model

    master

    The adaptive blur pipeline optimizes performance by selecting a downsample level based on the blur radius. The pipeline follows these steps:

    1. Level Selection: Chooses a downsample level using ceil(log2(radiusPx / denseRadiusPx)).
    2. Downsampling: Performs a series of downsamples using a 4-sample box pass.
    3. Blurring: Executes a horizontal and vertical separable blur at the selected level.
    4. Upsampling: Performs a series of upsamples using a linear sampler.

    Performance is measured in normalized full-resolution texture-read area units (A). A value of 1.000 A represents approximately one texture read for every full-size output pixel in the target area.

    For a selected level L and shader samples per blur pass S, the cost is calculated as:

    • downsample(L) = 4/3 * (1 - 4^-L)
    • upsample(L) = 4/3 * (1 - 4^-L)
    • blur(L) = 2 * S * 4^-L
    • total(L) = downsample(L) + blur(L) + upsample(L)
    downsample(L) = 4/3 * (1 - 4^-L)
    upsample(L)   = 4/3 * (1 - 4^-L)
    blur(L)       = 2 * S * 4^-L
    total(L)      = downsample(L) + blur(L) + upsample(L)
  6. How placement works with transparent noops

    master

    In the hypothetical design, placement for transparent nodes occurs in two distinct phases to ensure correct coordinate rebasing:

    Phase 1: Record Effective Child Placements

    When a parent layout places projected children, it does not call placeNode immediately. Instead, it records a PlacementRecord for each effective child:

    type PlacementRecord = {
      node: Layout
      transparentPath: Layout[]
      bounds: Rect
      proposal: ProposedSize
    }

    Phase 2: Resolve Transparent Ancestor Bounds

    After all projected children are placed, the engine computes a union for each transparent ancestor. Because a later child might expand the union of a transparent ancestor, the engine must rebase all layout rects in parent-local coordinates.

    Example of coordinate rebasing: If HStack places children X, Y, Z, and W, and A is a transparent ancestor of X, Y, and Z:

    • A's rect is calculated as the union of X, Y, and Z relative to HStack.
    • X's rect is then rebased to be local to A.

    This two-phase approach ensures that even if an ancestor's bounds change due to a later sibling, all descendants receive correct local coordinates.

  7. Browser and Runtime Requirements for Liquid DOM

    master

    Liquid DOM has specific hardware and browser feature requirements:

    • WebGPU: The liquid-glass renderer requires a browser with navigator.gpu support.
    • Three.js Integrations: These require Three's WebGPU renderer, not the standard WebGLRenderer.
    • DOM-backed HTML Content: To render live DOM content inside the canvas, you must use a browser that supports the experimental HTML-in-Canvas API.
      • Currently, this is only available in Chrome behind the flag: chrome://flags/#canvas-draw-element.
      • The implementation relies on <canvas layoutsubtree> and canvas paint events to copy DOM content into GPU textures.
  8. Understand the Backdrop and Output Flow

    master

    The @liquid-dom/three adapter does not manage your Three.js scene rendering. Instead, follow this flow:

    1. Render your Three scene into a target (like a RenderTarget or Texture).
    2. Pass that target as the backdrop option to glassRenderer.render().
    3. The liquid-glass pass samples that backdrop and writes the final composited result to the outputTexture (or the canvas texture if no output is specified).

    Important: Always call destroy() when the adapter is no longer needed to release GPU resources owned by the liquid-glass core.

  9. Use WebGpuGlassCore when writing adapters

    master

    When developing adapter packages, do not use the Renderer class directly, as the Renderer owns its own canvas. Instead, use WebGpuGlassCore for your adapter implementation.

    Important Lifecycle Note: Because DOM content is copied into GPU textures before glass rendering occurs, you must call the content source sync path before initiating the render cycle when writing an adapter.

  10. Work with LayoutNodes and geometry

    master

    LayoutNodes are mutable objects that form the layout tree. They have stable IDs and support parent-child relationships similar to the DOM (e.g., appending a node to a new parent automatically detaches it from the old one).

    Node Properties:

    • id, kind, parent, children, layout (the calculated geometry).
    • isLayoutActive(): Returns true if the node is reachable from an active LayoutEngine.root.

    Geometry Types:

    • layout.rect: The node's rectangle relative to its parent.
    • layout.absoluteRect: The node's rectangle relative to the LayoutEngine.root (accumulated through ancestors).

    Node Methods:

    • append, prepend, insertBefore, replaceChildren, remove, dispose.

    Lifecycle Hooks: Subclasses of Layout can override onLayoutActive() and onLayoutInactive() to manage external resources. These fire synchronously during tree mutations.

    Core Types:

    • ProposedSize: { width?: number; height?: number }
    • Size: { width: number; height: number }
    • Rect: { x: number; y: number; width: number; height: number }
    • NodeLayout: { rect: Rect; absoluteRect: Rect }
  11. Follow Node Relationship Rules for Glass

    master

    Because @liquid-dom/react synchronizes into the @liquid-dom/core scene graph, you must follow specific nesting rules for glass elements:

    1. GlassContainer must be the parent of Glass shapes.
    2. Glass must be the parent of Html content.
    3. Glass cannot be nested under another Glass.
    4. Layout components (like Frame or Transform) do not bypass these rules. A Glass nested under a Frame that is itself inside another Glass is still an invalid relationship.

    If you need to nest a Glass inside a Frame, ensure that Frame is not a child of another Glass node.

  12. Understand the hypothetical Transparent Noop Node design

    master

    This document describes a hypothetical future design for noop layout nodes in Liquid DOM. Currently, noop nodes are layout-neutral wrappers that require exactly one child because they pass measurement and placement through to that single child.

    The proposed design transforms noop nodes into transparent projection nodes.

    Key Characteristics:

    • No measurement/placement behavior: They do not participate in the layout process themselves.
    • Flattening: They are flattened into their nearest real layout parent. A parent like HStack would see the noop's children as its own direct effective children.
    • Projection: When a layout node asks for children, the engine returns 'projected effective children' along with their transparent ancestor path (e.g., X -> [A, B]).
    • Bounds derivation: While they don't have their own layout behavior, they still receive a layout rect derived from the union of their effective descendants. This allows wrappers (like scene graph nodes) to use that rect for transforms or state.
    • Restrictions: A noop cannot be the layout root and should not be measured directly (doing so is considered a bug).