slide-video-kit
v0.1.9
Published
JSON-driven slide/video engine with a frame-accurate timeline, CanvasKit rendering, React editor components and in-browser MP4 export.
Maintainers
Readme
slide-video-kit
JSON-driven slide/video engine with a frame-accurate timeline, CanvasKit rendering, React editor components and in-browser MP4 export. No FFmpeg required.
Install
npm install slide-video-kit react react-dom
# or
yarn add slide-video-kit react react-domreact ^18 || ^19 is a peer dependency. canvaskit-wasm and mediabunny are bundled as dependencies.
Quick start
Headless (React-free)
Use slide-video-kit/core and slide-video-kit/render to drive the engine from any JavaScript environment — no React needed.
import { SlideDefinition, SlideController, SlideWebBackend, SlideRender, LOCAL_FONT_OPTIONS } from 'slide-video-kit/core'
import { exportVideo, downloadBlob } from 'slide-video-kit/web-render'
const definition = new SlideDefinition()
definition.tracks.add('main')
definition.items.add({
type: 'text',
trackId: 'main',
start: 0,
duration: 5,
properties: { text: 'Hello, world!', fontSize: 64, fontFamily: 'sans-serif', color: '#ffffff' },
})
const controller = new SlideController(definition)
// In-browser MP4 export
const blob = await exportVideo(definition.getSnapshot().definition, { fps: 30 })
downloadBlob(blob, 'output.mp4')React editor
import { SlideDefinition, SlideController, SlidePlayer, SlideTimeline, SlidePropertiesPanel, ExportSettings } from 'slide-video-kit'
import 'slide-video-kit/styles.css'
const definition = new SlideDefinition()
const controller = new SlideController(definition)
const exportSettings = new ExportSettings() // optional: enables resolution select
export default function Editor() {
return (
<div>
<SlidePlayer definition={definition} controller={controller} exportSettings={exportSettings} />
<SlideTimeline definition={definition} controller={controller} exportSettings={exportSettings} />
<SlidePropertiesPanel definition={definition} controller={controller} />
</div>
)
}How to Use
Overview
The library is built around three pillars:
SlideDefinitionowns the document JSON: immutable operations, selection, block-level change events.SlideControllerowns the needle:play/pause/seek/tick, driven by whatever host it runs in.- React components (
SlidePlayer,SlideTimeline,SlidePropertiesPanel) subscribe to exactly the slice they display.
Both classes are pure JavaScript — no React, no browser APIs — so they run unchanged in Node.js, workers and unit tests.
DSL Syntax
The library includes a DSL (Domain-Specific Language) for defining timelines declaratively:
timeline {
width: 1920
height: 1080
quality: 1080p
track {
name: "Main Track"
text {
content: "Hello, world!"
x: 0
y: 0
width: 500
height: 500
opacity: 0.5
fontFamily: "Inter"
align: center
}
image {
src: "https://example.com/image.png"
x: 100
y: 100
width: 300
height: 200
}
}
}Key features:
timeline { }— root block containing the entire definitiontrack { }— individual track containing items (multiple tracks allowed)- Comments:
// line commentand/* block comment */ - Properties can be in any order within an item
- Automatic
startpositioning when omitted (sequential) - Overlap detection within tracks
- Strict validation of property names, values, and types per item
Item types: text, image, audio, video, rect, circle, ellipse, triangle, line
Valid fonts: sans-serif, Ubuntu, Nunito, Inter, Playfair Display, JetBrains Mono
Valid quality presets: 480p, 720p, 1080p, 1440p, 2160p
JSON Schema
Root — IVideoDefinition
interface IVideoDefinition {
width: number // base stage width in px (e.g. 1920)
height: number // base stage height in px (e.g. 1080)
tracks: ITrack[]
}
interface ITrack {
id: string // UUID
name?: string
items: TVideoItem[]
}Items — TVideoItem
type TVideoItem = IImageItem | ITextItem | IAudioItem | IShapeItem | IVideoItemBase
interface IItemBase {
id: string // UUID
start: number // seconds, relative to the video start
duration: number // seconds
}
interface IVisualBase extends IItemBase {
shadow?: IShadowProps
border?: IBorderProps
keyframes: TItemKeyframes
colorKeyframes?: TItemColorKeyframes
}
interface IBackgroundProps {
enabled?: boolean
kind: 'solid' | 'gradient'
color?: string // for solid
from?: string // for gradient
to?: string // for gradient
}
interface IShadowProps {
enabled?: boolean
color: string
}
interface IBorderProps {
enabled?: boolean
color: string
}IImageItem
interface IImageItem extends IVisualBase {
type: 'image'
src: string // URL or data URI
originalWidth?: number // native media width (auto-detected)
originalHeight?: number // native media height (auto-detected)
}ITextItem
interface ITextItem extends IVisualBase {
type: 'text'
content: string // text body
fontFamily?: string
align: 'left' | 'center' | 'right'
background?: IBackgroundProps
}
fontSize/fontWeightare not fixed fields — they are animatable properties delivered throughitem.keyframes(every new text item starts with default entries for them).
IAudioItem
type TMediaSource =
| { kind: 'url'; url: string }
| { kind: 'file'; dataUri: string }
interface IAudioItem extends IItemBase {
type: 'audio'
source: TMediaSource
keyframes: TItemKeyframes // drives `volume`
audioDuration?: number // real media duration (auto-detected)
}IVideoItem
interface IVideoItem extends IVisualBase {
type: 'video'
source: TMediaSource
videoDuration?: number // real media duration (auto-detected)
originalWidth?: number // native media width (auto-detected)
originalHeight?: number // native media height (auto-detected)
}IShapeItem
type TShapeKind = 'rect' | 'circle' | 'ellipse' | 'triangle' | 'line'
interface IShapeItem extends IVisualBase {
type: 'shape'
kind: TShapeKind
background?: IBackgroundProps
}
backgroundis only available on text and shape items — on text it acts as the glyph fill, on shapes as the box fill. Image and video items do not carry a background box.
Visual Properties
Visual properties are direct optional fields on each item (not a
transformers array). Availability per item type:
| Property | text | image | shape | video | |----------|:----:|:-----:|:-----:|:-----:| | background | ✓ | – | ✓ | – | | shadow | ✓ | ✓ | ✓ | ✓ | | border | – | ✓ | ✓ | ✓ |
(audio items support none.) Each property has an enabled flag — when
enabled === false the effect is inactive.
- background — solid color or linear gradient (
kind: 'solid' | 'gradient') - shadow — drop shadow behind the element; blur/offsets via the animatable
properties
shadowBlur,shadowOffsetX,shadowOffsetY - border — width + radius via the animatable properties
borderWidthandborderRadius
Colors are also keyframeable through colorKeyframes
(backgroundColor, backgroundFrom, backgroundTo, shadowColor,
borderColor).
Keyframes
type TAnimatableProperty =
| 'x' | 'y' | 'width' | 'height'
| 'rotation' | 'opacity' | 'scale'
| 'fontSize' | 'fontWeight' | 'volume'
| 'backgroundAngle' | 'shadowBlur' | 'shadowOffsetX' | 'shadowOffsetY'
| 'borderWidth' | 'borderRadius'
type TAnimatableColorProperty =
| 'backgroundColor' | 'backgroundFrom' | 'backgroundTo'
| 'shadowColor' | 'borderColor'
type TAnimatablePropertyOrColor = TAnimatableProperty | TAnimatableColorProperty
// Numeric keyframes (time string → number):
type TItemKeyframes = Partial<Record<TAnimatableProperty, Record<string, number>>>
// Color keyframes (time string → color string):
type TItemColorKeyframes = Partial<Record<TAnimatableColorProperty, Record<string, string>>>Items carry two maps — animatable property → keyframe record (time string →
value); an absent property is not animated. Visual items carry the
spatial/visual properties; audio items carry volume; video items support
all of the above including volume.
Time invariant: a keyframe never lives outside [0, item.duration]
(keyframe times are relative to the item). Write APIs (set, setProperty,
setItem, toggle, update) throw when given an out-of-range time. When a
duration shrinks, keyframes beyond the new end collapse into a single
keyframe at duration — the one with the greatest original time wins — while
in-range keyframes stay where they are; items.update emits an extra
keyframes.set-many event listing every affected property.
SlideDefinition API
import { SlideDefinition } from 'slide-video-kit/core'
const doc = new SlideDefinition(initialDefinition?) // defaults to an empty 1920×1080 documentStore protocol
| Member | Description |
|--------|-------------|
| subscribe(listener) | Notified after every effective change. Stable identity — pass it straight to useSyncExternalStore. Returns unsubscribe. |
| getSnapshot() | Cached { definition, duration, selectedId }. Identity changes only when some field actually changes (no-op mutations skip both the object and the notification). |
Point-in-time reads
| Getter | Description |
|--------|-------------|
| definition | Current IVideoDefinition (read-only by convention). |
| duration | Total duration in seconds (longest item end). Recomputed on every commit. |
| selectedId | Selected item id, or null. |
Commands
| Method | Description |
|--------|-------------|
| select(id \| null) | Sets the selection. No-op when equal to current. Emits a selection event. |
| onSelectionChange(listener) | Channel for selection changes → { selectedId }. |
| mutate(reducer) | Escape hatch: arbitrary (definition) => definition transform through the same reactive pipeline. Notifies the global channel only — block channels receive no events (affected domains are unknowable here). |
| batch(fn) | Groups mutations: the global channel is notified once when fn returns, and queued block events are delivered afterwards in order. Nested calls flush at the outermost boundary. |
Block channels (with mutation context)
Each domain namespace carries its own subscribe(listener). Events are
emitted synchronously after the commit, in order: snapshot update → global
channel → block channel. No-op mutations emit nothing.
| Channel | Event payloads |
|---------|----------------|
| tracks.subscribe | { action: 'add', trackId } · { action: 'remove', trackId } · { action: 'rename', trackId, name } · { action: 'move', fromIndex, toIndex } |
| items.subscribe | { action: 'add', trackId, itemId } · { action: 'update', trackId, itemId, patch } · { action: 'remove', trackId, itemId } · { action: 'move', fromTrackId, toTrackId, itemId, start } |
| keyframes.subscribe | { action: 'set', itemId, property, time, value } · { action: 'set-many', itemId, properties } · { action: 'remove', itemId, property, time } |
Domain methods also guard against no-ops: removing/renaming a nonexistent track or updating an unknown item emits neither global nor block events.
Domain methods
| Namespace | Methods |
|-----------|---------|
| tracks | list(), get(id), first(), findIndex(id), add(id?, name?) (deterministic id; no-op when the id exists), remove(id), rename(id, name), move(fromIndex, toIndex) |
| items | add(trackId, type, opts?) (opts: { at?, id?, duration? }), update(itemId, patch), remove(itemId), get(itemId), getTrack(itemId), move(itemId, toTrackId, start), resolveMediaSource(itemId), addWithGap(trackId, type, at, maxDuration?), updateBackground(itemId, partial), updateShadow(itemId, partial), updateBorder(itemId, partial), toggleCategory(itemId, category, enabled), canPlace(itemId, start, duration) |
| keyframes | set(itemId, property, time, value), setProperty(itemId, property, list) (replaces the whole property list), setItem(itemId, patch) (replaces several properties at once, single event), remove(itemId, property, time), has(itemId, property, time), evaluateItem(itemId, relativeTime), getPropertyValue(itemId, property, time), resolveField(itemId, property, relativeTime), resetDefaults(itemId, properties[]), evaluateColors(itemId, relativeTime), getColorValue(itemId, property, time), resolveColorField(itemId, property, relativeTime), toggle(itemId, property, time, value), update(itemId, property, time, value) |
items.add defaults (base duration 5s, keyframe map pre-created):
| Type | Defaults |
|------|----------|
| text | content "Novo texto", align center, shadow enabled (rgba(0,0,0,0.6)); keyframes: x, y, width, height (= def size), rotation, opacity, scale, fontSize (64), fontWeight (600), shadowBlur (8), shadowOffsetX (2), shadowOffsetY (2) |
| image | src: inline gradient SVG (blue→purple); keyframes: x, y, width, height (= def size), rotation, opacity, scale |
| audio | source: generated 440 Hz WAV tone (3s); keyframes: volume (default 0.8) |
| video | source: empty (user provides URL or file); keyframes: x, y, width, height (= def size), rotation, opacity, scale, volume (default 0.8) |
| shape | kind rect, background seeded (solid white, enabled), offset from center via x/y keyframes; keyframes: x, y, width, height, rotation, opacity, scale |
videoDuration/audioDuration are unset at creation — the player detects
the real media duration on load and auto-fits the item.
SlideController API
import { SlideController } from 'slide-video-kit/core'
const controller = new SlideController(doc)
// `doc` only needs: { readonly duration: number }The controller accepts any IDurationSource — a SlideDefinition
satisfies it structurally, and the duration is re-read live on every
operation, so seek clamping and end detection always follow document edits.
Channels
| Channel | Members | Fires on |
|---------|---------|----------|
| State | subscribe(listener), getSnapshot() → { currentTime, playing, loop, playbackRate } | play, pause, seek, natural end, loop/rate changes |
| Frames | onFrame(callback) → (time: number) => void | every host-driven tick (60fps); does NOT re-render React |
Commands & reads
| Member | Description |
|--------|-------------|
| play() | Starts playback; restarts from 0 if at the end. The next tick only anchors (no jump). |
| pause() | Freezes at the current internal time. |
| seek(time) | Clamps to [0, live duration]; while playing continues from there (re-anchors so elapsed time doesn't jump). No-op when unchanged. |
| stepForward(frames?) / stepBackward(frames?) | Nudges the needle by frames frame-steps (1/30s each, default 1) and pauses. |
| setLoop(loop) | Loop mode: when enabled, reaching the duration wraps time back to 0 instead of ending. |
| setPlaybackRate(rate) | Speed multiplier applied on every subsequent tick and to the media engines. |
| tick(nowMs) | The only way time advances. Host provides the timestamp (in the browser, a rAF hook). First call after play/seek anchors; later calls advance by delta scaled by playbackRate; reaching the duration wraps (loop) or emits a final frame, auto-pauses and notifies. Ignored while not playing. |
| currentTime / playing / duration / loop / playbackRate | Live getters (duration delegates to the source). |
Because time math is pure, tests can feed synthetic timestamps and backend jobs can use seek-only flows without ever ticking.
React Integration
useDefinitionSelector
const selectedId = useDefinitionSelector(definition, (s) => s.selectedId)
const playing = useControllerSelector(controller, (s) => s.playing)
const tracks = useDefinitionSelector(
definition,
(s) => s.definition.tracks,
shallowEqualArray, // list projections keep identity via structural sharing
)A component re-renders only when its projected value actually changes
(reference equality by default, custom isEqual supported). Immutable
operations preserve untouched references, so unrelated edits bail out here.
useControllerDriver
Owns everything host-specific:
useControllerDriver(controller)
// - runs requestAnimationFrame while playing → controller.tick(performance.now())
// - auto-pauses when the tab is hidden (visibilitychange)Mount it once, inside SlidePlayer. Every other consumer follows frames
through controller.onFrame. Effect teardown makes StrictMode/HMR safe by
construction — the classes hold no globals, so there is nothing else to
clean up.
useMediaDimensionProbe
Probes original dimensions of image and video items, then auto-fits them to the definition canvas using contain-fit (preserving aspect ratio):
useMediaDimensionProbe(definition)
// - probes native width/height of images and videos
// - stores originalWidth/originalHeight on the item
// - when keyframes are still at defaults, resizes to contain-fit and centers
// - when keyframes were customized, preserves author's layout
// - re-probes when source changes (dimensions differ from stored originals)Mount it once, inside SlidePlayer. The hook is idempotent — items
with matching originalWidth/originalHeight are skipped.
Self-subscribing pattern
Components consume directly what they display:
// Clip subscribes to its own item + selection flag + duration:
const item = useDefinitionSelector(definition, selectItem(itemId))
const isSelected = useDefinitionSelector(
definition,
useCallback((s) => s.selectedId === itemId, [itemId]),
)Selecting an item re-renders exactly two clips (previous and next).
Dragging one clip re-renders that clip alone. List-heavy leaves (Clip,
Lane, Ruler, PlayControls…) are explicitly memo-wrapped; the
remaining components rely on the React Compiler preset for automatic
memoization. Containers subscribe to nothing store-related, so top-of-tree
re-renders don't cascade.
Core Components
SlidePlayer
Renders the stage on a single <canvas> through CanvasKit's raster surface
plus transport controls.
Internals:
applyFrame(time)— registered ononFrame; updates the renderer definition, renders the frame, and syncs the audio/video engines against the master clock — no React re-render at 60fps (progress bar and timecode are direct DOM writes).- A
definition.subscribe(...)effect re-applies the current frame after every document commit. SlideRenderowns the render pipeline: evaluates keyframes per active item and dispatches to text/image/shape/video renderers over anIRenderBackend(SlideWebBackend= CanvasKit).- Video elements live in the renderer's asset store keyed by item id; each
is registered into
VideoEngineso play/pause/seek/volume follow the needle. Duration is auto-detected byuseVideoDurationProbe. - Fonts: local families registered at backend init; remote families load lazily on first use.
AudioEngine — one <audio> element per item (elements are created,
registered and auto-fit by the player), routed through Web Audio gain nodes
when an AudioContext is available:
| Method | Purpose |
|--------|---------|
| register/unregister(id) | Element lifecycle (owned by the player) |
| sync(items, now, playing, playbackRate) | Locks active elements to the master clock (seek tolerance + play/pause) |
| applyVolumeKeyframes(items, now) | Ramps gain toward the evaluated volume (GainNode ramps; falls back to element.volume) |
| setPlaybackRate(rate) | Applies the transport rate to every element |
| resume/pause/reset/close | Context lifecycle |
VideoEngine — keeps <video> elements locked to the master clock:
| Method | Purpose |
|--------|---------|
| register/unregister(id) | Element lifecycle (owned by the player) |
| sync(items, now, playing, playbackRate) | Locks active elements to the master clock (seek tolerance + play/pause + playback rate) |
| applyVolumeKeyframes(items, now) | Applies volume keyframes via element.volume |
| setPlaybackRate(rate) | Applies the transport rate to every element |
| probeDuration(src) | Static: reads media duration via preload="metadata" |
| probeDimensions(src) | Static: reads native width/height via preload="metadata" |
| pause/resume/reset/close | Lifecycle (pause all elements / no-op / alias / clear map) |
PlayControls — minimal transport bar: play/pause button and gear menu
button on the left, seek bar in the center, timecode on the right. The gear
menu contains: playback rate select, quality/resolution select (when
exportSettings is provided), loop toggle, and restart button.
Global hotkeys: Space play/pause, L loop toggle.
SlideTimeline
Interaction shell — subscribes to nothing; handlers read live state through
definition.definition.
Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| definition | SlideDefinition | Yes | The slide definition instance |
| controller | SlideController | Yes | The controller instance |
| exportSettings | ExportSettings | No | When provided, the resolution select appears in the toolbar. Create with new ExportSettings() and pass the same instance to SlidePlayer for synced resolution control. |
import { SlideTimeline, ExportSettings } from 'slide-video-kit'
const exportSettings = new ExportSettings()
<SlideTimeline
definition={definition}
controller={controller}
exportSettings={exportSettings} // optional — enables resolution select
/>Sub-components: TimelineToolbar (palette + play/pause + frame step +
timecode + resolution select, self-subscribed), PaletteButton, Ruler
(adaptive ticks, self-subscribed to duration), Lanes (owns the active-lane
state; subscribes to track ids/names projections), Lane → LaneTrack
(subscribes to its own item-id list) → Clip (subscribes to its own item,
selection flag and duration), LaneGutter, AddTimelineButton, Ghost.
Drag modes (6): scrub (ruler), create (palette drag), move
(intra/inter-lane), start/end (resize handles), reorder (lane handle).
Playhead positioning uses calc() percentages — window resizes keep it
proportional without any JS listener.
SlidePropertiesPanel
Subscribes to the selected item only (plus primitives), so edits to other elements never touch it.
Fields follow the needle during playback through a throttled display clock: state-channel updates (seek/pause/end) land exact and unthrottled, frame updates repaint at most every 50ms.
Sub-components: PanelHeader, Category, NumberField (editor-style:
scrub by drag, click to edit, relative operators +10/-5/*2, arrows with
Shift ×10 / Alt ×0.1), TextField, TextAreaField, ColorField +
ColorPicker, UnifiedSelect (adaptive group-button/select), NumberInput,
KeyframeControl (diamond + prev/next), ToggleSwitch, visual property
sections (background, shadow, border — each with on/off toggle), media
fields for audio/video.
Keyframe field behavior:
- Shows the interpolated value when the needle sits between keyframes
- Enabled when there is a keyframe at the needle OR after the last one; disabled between two keyframes (create one via the diamond to edit)
- Editing after the last keyframe rewrites that last keyframe
Editor Capabilities
Tracks
- Create / remove / rename (inline input) / reorder (drag handle)
- Tracks overlap in time (visual layers); items inside a track never do (automatic collision resolution)
Items
- Create by palette click (active lane, at the needle) or drag-and-drop
- Drag within/between lanes with live collision checks
- Resize through left/right handles
- Select (panel reacts) / remove
- Per-item visual properties (background, shadow, border) and full keyframe support
- Types: text, image, audio, video, shape
Visual Properties
background(solid/gradient — text/shape only),shadow,border— direct properties on each visual item, each toggleable viaenabled
Keyframes
- Animatable numeric: x, y, width, height, rotation, opacity, scale, fontSize, fontWeight, volume, backgroundAngle, shadowBlur, shadowOffsetX, shadowOffsetY, borderWidth, borderRadius
- Animatable colors (via
colorKeyframes): backgroundColor, backgroundFrom, backgroundTo, shadowColor, borderColor - Keyframes are always clamped to the item interval
[0, duration]— shrinking a clip collapses trailing keyframes into its new end - Linear interpolation evaluated at 60fps (imperative path)
- Diamond toggle + prev/next navigation in the panel; interpolated values on display; inputs disabled between keyframes
Audio
- URL or file upload (data URI)
- Volume keyframes (fades), real-duration auto-fit on load (
audioDuration) - Elements locked to the master clock through
AudioEngine, with gain-node volume ramps and playback-rate support
Video
- URL or file upload; frames drawn onto the canvas each tick through
drawVideoFrame - Locked to the master clock through
VideoEngine(see tolerances above) - Full transform/keyframe support incl.
volumekeyframes - Real-duration auto-fit (
videoDuration) - Intrinsic dimension probe + contain-fit auto-sizing on creation
DSL
- Declarative timeline definition via
.slideVideofiles - Comments:
// line commentand/* block comment */ - Strict validation: property names, values, ranges, and types per item
- Error recovery: continues parsing after errors to find multiple issues
- Overlap detection within tracks
- Automatic
startpositioning when omitted - Gradient shorthand:
backgroundFrom/backgroundTo(notbackgroundColorGradientStart/End) - Quality presets:
480p,720p,1080p,1440p,2160p - Font validation: only system fonts are accepted
Web Render / Export
slide-video-kit/web-render provides offline video export without FFmpeg.
It renders each frame to an offscreen <canvas>, mixes audio via
OfflineAudioContext, and records the combined stream through
mediabunny (MP4 via WebCodecs).
import { exportVideo, downloadBlob } from 'slide-video-kit/web-render'
const blob = await exportVideo(definition, { fps: 30, resolution: '1080p', onProgress })
downloadBlob(blob)Resolution presets
| Preset | Label | Target height | Video bitrate |
|--------|-------|---------------|---------------|
| 480p | 480p · SD | 480 | 3 Mbps |
| 720p | 720p · HD | 720 | 5 Mbps |
| 1080p | 1080p · Full HD | 1080 | 8 Mbps |
| 1440p | 1440p · QHD | 1440 | 14 Mbps |
| 2160p | 2160p · Ultra HD 4K | 2160 | 30 Mbps |
Default: 1080p. Dimensions are scaled proportionally from the definition's
width × height, rounded to even numbers (encoder requirement). The
ExportSettings class holds the active preset with a subscribe/getSnapshot
store protocol — any UI component can bind to it.
| Module | Purpose |
|--------|---------|
| ExportVideo.ts | Orchestrates the full pipeline: load assets → render audio → capture frames → mux to MP4 |
| ExportResolution.ts | Resolution presets table, dimension helpers, bitrate resolver |
| ExportSettings.ts | Store class for export resolution (subscribe/getSnapshot/setResolution) |
| AudioRenderer.ts | Mixes all audio tracks via OfflineAudioContext with volume keyframes |
| AssetLoader.ts | Pre-loads images, audio buffers and video elements before recording |
| WebAssetLoader.ts | Image/video loader using HTMLImageElement and HTMLVideoElement |
| LocalFonts.ts | Bundled font registration options for Vite ?url imports |
| Types.ts | IRenderOptions (fps, resolution, onProgress) + domain type re-exports |
Subpath exports
| Import path | Contents | React |
|---|---|---|
| slide-video-kit | Everything (aggregates all subpaths below) | Yes |
| slide-video-kit/core | SlideDefinition, SlideController, operations, domain types | No |
| slide-video-kit/render | SlideRender, SlideWebBackend, font catalog, LOCAL_FONT_OPTIONS | No |
| slide-video-kit/web-render | exportVideo, downloadBlob (WebCodecs MP4) | No |
| slide-video-kit/player | SlidePlayer component | Yes |
| slide-video-kit/timeline | SlideTimeline component | Yes |
| slide-video-kit/properties | SlidePropertiesPanel component | Yes |
| slide-video-kit/styles.css | Stylesheet for all React components | — |
Import only what you need. Core and render subpaths are fully React-free.
Fonts & WASM assets
Fonts and CanvasKit WASM are inlined as data URIs in the library bundle (Vite lib mode default). This makes the package self-contained — it works in any bundler without extra configuration.
To use CDN-hosted or custom assets instead, override the options:
const backend = new SlideWebBackend({
locateFile: (file) => `https://cdn.example.com/canvaskit/${file}`,
fonts: [
{ familyName: 'sans-serif', source: '/fonts/roboto-400.ttf' },
// ...
],
})Browser support
The MP4 export (web-render) requires WebCodecs support (Chrome 94+, Edge 94+, Safari 16.4+).
Glossary
| Term | Meaning |
|------|---------|
| Definition | The JSON document (IVideoDefinition) owned by SlideDefinition — acts as the timeline |
| Needle | Current playback position owned by SlideController |
| Track | Data row where clips live inside a timeline |
| Gutter | Track sidebar (name, reorder handle, remove button) |
| Clip | Colored rectangle representing an item on the track |
| Ruler | Time ruler with adaptive ticks; doubles as scrub surface |
| Playhead | Vertical needle marking the current time |
| Ghost | Cursor-following preview while dragging from the palette |
| Visual Property | Background, shadow or border effect with enabled flag |
| Keyframe | Time/value point on an animatable property track |
| Palette | Item-type buttons used to create items |
Third-party licenses
This package bundles code from the following projects. Their license texts are included in the LICENSES/ directory of the published package:
| Package | License | Copyright | |---|---|---| | canvaskit-wasm | BSD-3-Clause | Google Inc. | | mediabunny | MPL-2.0 | nicbarker |
