VideoFlowcodeGitHubTry itCoreRenderersReact Video EditorPlaygroundExamplesDocscodeGitHubTry it
Getting started
InstallationQuick startCore conceptsYour first video
Builder
Builder APITime formatsParallel & wait
Layers
TextImageVideoAudioCaptionsShapeGroups
Animation
Animate & keyframesEasing functionsTransitionsEffects
Renderers
Browser rendererServer rendererDOM preview
React Video Editor
QuickstartThemingUploadsCustom panelsHooks & commandsKeyboard shortcuts
API reference
Overview@videoflow/core@videoflow/renderer-browser@videoflow/renderer-server@videoflow/renderer-dom@videoflow/react-video-editor
API reference

@videoflow/renderer-dom

Live, scrubbable player that paints VideoJSON into a DOM host.

Classes

class

DomRenderer

Re-exported from `@videoflow/renderer-browser` so an app can build an external layer type against `@videoflow/renderer-dom` alone: ```ts import DomRenderer, { RuntimeVisualLayer, type LayerTypeDescriptor } from '@videoflow/renderer-dom'; class RuntimeCustomLayer extends RuntimeVisualLayer { ... } const renderer = new DomRenderer(host); renderer.registerLayerType('custom', { runtime: RuntimeCustomLayer, propertiesDefinition: CustomLayer.propertiesDefinition, }); await renderer.loadVideo(videoJSON); ```

Methods

constructor
(host: HTMLElement): DomRenderer
Parameters
NameTypeDescription
hostHTMLElement
Returns
DomRenderer
addLayer
(layerJSON: LayerJSON, index?: number): Promise<void>

Insert a new layer into the video at the given index (defaults to end). The new layer is constructed, initialized, and mounted into the existing `$canvas` — other layers' DOM elements are left untouched.

Parameters
NameTypeDescription
layerJSONLayerJSONThe layer JSON to add. Must include a unique `id`.
index optionalnumberThe insertion index in `this.layers`. Defaults to appending.
Returns
Promise<void>
compositeLayerInto
(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, layer: RuntimeBaseLayer, frame?: number): Promise<void>

Rasterize a single layer (cached when possible), pipe it through the WebGL effect compositor if it declares effects, and `drawImage` the result onto `ctx`. Used by `RuntimeGroupLayer.renderFrame` to flatten each child onto the group's canvas. The child's `blendMode` is honoured here via `globalCompositeOperation` so children inside a group blend identically in live preview and export. Without this, group-children blend modes would silently no-op in DomRenderer because the children render on the off-screen `virtualRoot` (their CSS `mix-blend-mode` never has a chance to take effect) — only the group's own composited canvas is on-screen.

Parameters
NameTypeDescription
ctxCanvasRenderingContext2D | OffscreenCanvasRenderingContext2D
layerRuntimeBaseLayer
frame optionalnumber
Returns
Promise<void>
createRuntimeLayer
(layerJSON: LayerJSON): RuntimeBaseLayer

Instantiate the runtime layer registered for `layerJSON.type`, wired to this renderer and the active project's fps / dimensions. Used for top-level layers and — via `ILayerRenderer` — by groups for their descendants. Throws for a type that isn't registered on this renderer.

Parameters
NameTypeDescription
layerJSONLayerJSON
Returns
RuntimeBaseLayer
destroy
(clearShadow: boolean): void

Tear down the renderer: stop playback (also exits smooth-playback mode on every video layer), destroy every runtime layer (releases media elements, decoders, and `requestVideoFrameCallback` subscriptions), clear the per-layer effect canvases, dispose the rasterizer's per-layer surfaces and the WebGL effect compositor's GL context, and finally empty the shadow DOM. Pending queued mutations registered before destroy resolve to no-ops via the `destroyed` guard.

Parameters
NameTypeDescription
clearShadowbooleanWhether to wipe the shadow DOM. Default `true`. Pass `false` if the host element is being removed anyway and you want to keep the last rendered frame visible until removal.
getLayerType
(type: string): LayerTypeDescriptor | undefined

The descriptor registered for `type` on this renderer, if any.

Parameters
NameTypeDescription
typestring
Returns
LayerTypeDescriptor | undefined
getPropertyDefinition
(layerType: string): Record<string, PropertyDefinition> | undefined

Return the full propertiesDefinition for a layer type, or a single property's definition. Resolved through this renderer's layer-type registry, so an overridden type gets its overriding property definitions too.

Parameters
NameTypeDescription
layerTypestring
Returns
Record<string, PropertyDefinition> | undefined
(layerType: string, prop: string): PropertyDefinition | undefined

Return the full propertiesDefinition for a layer type, or a single property's definition. Resolved through this renderer's layer-type registry, so an overridden type gets its overriding property definitions too.

Parameters
NameTypeDescription
layerTypestring
propstring
Returns
PropertyDefinition | undefined
getVirtualLayerHost
(): Node

Where group layers should park their hidden child host. We use the shadow root so renderer CSS (which is shadow-scoped) still applies to the group's children — without this, descendants would lose their `--vw` / `--project-*` context and render at the wrong scale.

Returns
Node
listLayerTypes
(): string[]

Every layer type registered on this renderer, built-ins included.

Returns
string[]
loadFont
(fontName: string): Promise<void>

Load a Google Font and make it available to the document (and shadow DOM).

Parameters
NameTypeDescription
fontNamestring
Returns
Promise<void>
loadVideo
(videoJSON: VideoJSON): Promise<void>

Load a compiled VideoJSON into the renderer. Sets up the shadow DOM, creates runtime layers, initialises media, and renders frame 0. Closes the registerLayerType window — synchronously on entry, so the cutoff doesn't depend on when the mutation queue gets around to the actual load.

Parameters
NameTypeDescription
videoJSONVideoJSONCompiled VideoJSON from VideoFlow.compile().
Returns
Promise<void>
play
(options: { fpsCallback?: (fps: number) => void }): Promise<void>

Start real-time playback from the current frame with audio sync. Renders audio to WAV, creates an `Audio` element, and advances frames inside a `requestAnimationFrame` loop while nudging the audio `playbackRate` to keep video and audio in sync. Frame dropping -------------- Targets are computed from wall-clock time (`Date.now() - startTime`) and each `renderFrame` call is fired-and-forget — playback never waits for a render to finish. If the renderer can't keep up (a single frame takes longer than one tick), renderFrame's internal `pendingFrame` slot supersedes the in-flight target with the latest one, so intermediates are skipped rather than serialized. Wall-clock time and audio stay live; the picture catches up to the most recent frame the renderer can produce. Each layer is switched into its smooth-playback path via RuntimeBaseLayer.enterSmoothPlayback at the start of the loop — for video layers this trades the per-frame `currentTime` seek for a native `<video>.play()` plus drift correction, eliminating the seek cost that otherwise dominates live-preview frame budget. The path is reverted on `stop()` / `seek()` so scrubbing stays frame-accurate.

Parameters
NameTypeDescription
options{ fpsCallback?: (fps: number) => void }Optional callbacks: - `fpsCallback(fps)` — fired on each successful render with the rolling 1-second count of actually-rendered frames. With frame dropping this can sit below the video's nominal fps; that's the meaningful signal (HUD / diagnostic display). To track frame changes, set the public onFrame property.
Returns
Promise<void>
registerLayerType
(type: string, descriptor: LayerTypeDescriptor): void

Register (or replace) a layer type on **this** renderer. ```ts const renderer = new DomRenderer(host); renderer.registerLayerType('custom', { runtime: RuntimeCustomLayer, propertiesDefinition: CustomLayer.propertiesDefinition, }); await renderer.loadVideo(videoJSON); ``` Lifecycle: register after construction and **before** the first `loadVideo()`. Registrations stay attached to this instance, so every later `loadVideo()` / `addLayer()` uses them without re-registering. Registering after a video has been loaded throws rather than silently rebuilding the live runtime layers (which would drop their loaded media and mounted DOM) — construct a new DomRenderer instead. Registering a type that already exists (including a built-in) replaces the previous descriptor rather than throwing. The registry belongs to this instance: other renderers are unaffected.

Parameters
NameTypeDescription
typestring
descriptorLayerTypeDescriptor
removeLayer
(id: string): Promise<void>

Remove a layer from the video. Destroys the layer (releasing its media ref) and detaches its DOM element. Other layers are untouched.

Parameters
NameTypeDescription
idstring
Returns
Promise<void>
renderAudio
(): Promise<AudioBuffer | null>

Render the full audio track as an AudioBuffer. Recurses through groups via the shared mixer — each group renders into its own intermediate buffer so the group's own `volume` / `pan` / `pitch` / `mute` / transitions apply to the whole sub-mix.

Returns
Promise<AudioBuffer | null>
renderFrame
(frame: number, force: boolean): Promise<void>

Render a specific frame to the DOM. Skips if already at that frame (unless forced), queues if a render is already in progress.

Parameters
NameTypeDescription
framenumberFrame number to render.
forcebooleanRender even if already at this frame.
Returns
Promise<void>
reorderLayers
(orderedIds: string[]): Promise<void>

Reorder layers to match the given id sequence. The runtime layers and the backing `videoJSON.layers` array are reordered, and the layers' `$element`s are re-appended to `$canvas` in the new order. For layers without a `track`, DOM order drives paint order. Layers with a `track` carry an explicit z-index (`track + 1`) written in `applyProperties`, so reordering them in the array without changing their `track` only changes DOM order, not visual stacking. No media is touched.

Parameters
NameTypeDescription
orderedIdsstring[]The new layer order. Must contain exactly the set of currently-loaded layer ids, in any order. Extra/missing ids throw.
Returns
Promise<void>
seek
(frame: number): Promise<void>

Seek to a frame. If playback is active, stops it (which exits smooth mode), renders the target frame deterministically through the seek path, then restarts playback (re-entering smooth mode). When playback is not active, the frame is decoded by the seek path directly — pixel-deterministic, the same path used by export.

Parameters
NameTypeDescription
framenumber
Returns
Promise<void>
stop
(): void

Stop playback. Bumps the play-token (so any loop suspended in `await renderAudio()` / `requestAnimationFrame` exits without running its cleanup), tears down the `Audio` element, and reverts every layer's smooth-playback hook so the next `renderFrame` (typically from `seek()` or `currentTime =`) decodes the exact requested timestamp instead of whatever the smooth decoder happened to be presenting.

updateLayer
(id: string, patch: { animations?: Animation[]; effects?: any[]; properties?: Record<string, any>; settings?: Partial<LayerSettingsJSON>; track?: number | null; transitionIn?: any; transitionOut?: any }): Promise<void>

Apply a property / settings / animations patch to a single layer. - `settings` is shallow-merged into `layer.json.settings`. - `properties` replaces `layer.json.properties` wholesale so keys removed by the editor (e.g. via a reset-to-default) are actually cleared. Callers should pass the full post-mutation properties object. - `animations` replaces the array wholesale (same rationale — callers hold the diffing logic because per-keyframe reconciliation is cheap to do in editor state). If `settings.source` changed, the layer's media is re-initialized via `layer.initialize()` — callers should debounce rapid source swaps. If a text layer's `fontFamily` is among the patched properties, the font is loaded into the shadow DOM before the frame is re-rendered.

Parameters
NameTypeDescription
idstringThe layer id to patch.
patch{ animations?: Animation[]; effects?: any[]; properties?: Record<string, any>; settings?: Partial<LayerSettingsJSON>; track?: number | null; transitionIn?: any; transitionOut?: any }A partial patch. Any subset of settings/properties/animations.
Returns
Promise<void>
updateVideo
(patch: { backgroundColor?: string; duration?: number; height?: number; name?: string; width?: number }): Promise<void>

Patch top-level video properties (width, height, backgroundColor, name, duration). `fps` changes are not supported here — they invalidate frame numbers across the pipeline and require a full `loadVideo()`. `duration` is safe to update incrementally: it's only used as the loop bound in `play()` and the divisor in the `totalFrames` getter; layer frame numbers depend on `fps`, not `duration`. An in-flight `play()` call captures `durationSec` at start, so the new bound takes effect on the next `play()` invocation.

Parameters
NameTypeDescription
patch{ backgroundColor?: string; duration?: number; height?: number; name?: string; width?: number }
Returns
Promise<void>
registerEffect
(name: string, glsl: string, params: Record<string, EffectParamDefinition>): void

Register a GLSL effect. Shares its registry with `BrowserRenderer`.

Parameters
NameTypeDescription
namestring
glslstring
paramsRecord<string, EffectParamDefinition>
registerTransition
(name: string, fn: TransitionFn): void

Register a transition preset. The registry is shared with `BrowserRenderer` so a transition registered on either renderer is available in both live preview and export.

Parameters
NameTypeDescription
namestring
fnTransitionFn

Properties

NameTypeDescription
currentFramenumberCurrent frame rendered.
layerByIdMap<string, RuntimeBaseLayer>Fast id → runtime layer lookup, kept in sync with `layers`.
layersRuntimeBaseLayer[]Runtime layer instances.
onFrame(frame: number) => void | nullOptional callback fired whenever a new frame is rendered. Set this externally to keep a UI (seek bar, time label, …) in sync with playback.
playingbooleanWhether playback is active.
currentTime
duration
fps
totalFrames

Type aliases

type

DomRendererCallback

type DomRendererCallback = (event: string, data: any) => void

On this page

ClassesType aliases
VideoFlow

Open-source toolkit for composing videos from code.

Product

CoreRenderersReact Video EditorPlayground

Learn

DocsAPI referenceExamplesvs. Remotionvs. FFmpeg

Project

GitHubLicenseContactTermsPrivacy

From the blog

All posts →The 50-Line YouTube Shorts Factory: Programmatic Video with VideoFlowMastering Motion: How to Animate Effect Parameters in VideoFlowMastering Flow: How to Use $.wait, $.parallel, and $.group in VideoFlowParallel vs Sequential: Mastering the VideoFlow Timing ModelOne JSON, Three Renderers: Mastering the VideoFlow LifecycleGenerating Multi-Language Video at Scale: The Localization PlaybookBrowser-Side Video Export: Zero-Server Rendering with WebCodecsHow to Build a Dynamic Video Editor in React in 10 Minutes
© 2026 VideoFlow. Apache-2.0 core.