npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

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-dom

react ^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:

  • SlideDefinition owns the document JSON: immutable operations, selection, block-level change events.
  • SlideController owns 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 definition
  • track { } — individual track containing items (multiple tracks allowed)
  • Comments: // line comment and /* block comment */
  • Properties can be in any order within an item
  • Automatic start positioning 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 | IVideoItem
Base
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/fontWeight are not fixed fields — they are animatable properties delivered through item.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
}

background is 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 borderWidth and borderRadius

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 document

Store 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 on onFrame; 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.
  • SlideRender owns the render pipeline: evaluates keyframes per active item and dispatches to text/image/shape/video renderers over an IRenderBackend (SlideWebBackend = CanvasKit).
  • Video elements live in the renderer's asset store keyed by item id; each is registered into VideoEngine so play/pause/seek/volume follow the needle. Duration is auto-detected by useVideoDurationProbe.
  • 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), LaneLaneTrack (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 via enabled

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. volume keyframes
  • Real-duration auto-fit (videoDuration)
  • Intrinsic dimension probe + contain-fit auto-sizing on creation

DSL

  • Declarative timeline definition via .slideVideo files
  • Comments: // line comment and /* 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 start positioning when omitted
  • Gradient shorthand: backgroundFrom/backgroundTo (not backgroundColorGradientStart/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 |

License

MIT — see LICENSE. Third-party licenses in LICENSES/.