Skip to Content
New: @blazediff/agent - agentic visual regression your coding agent can judge. Read more β†’
APIs@blazediff/ui

@blazediff/ui

Headless engine and a framework-agnostic renderer for building image comparison interfaces. Works with any JavaScript framework or vanilla HTML.

Installation

npm install @blazediff/ui

Two layers

  • @blazediff/ui β€” a tiny pure-JS renderer. mount* functions build the DOM, wire events, and keep it updated.
  • @blazediff/ui/engine β€” the headless engine. All state, calculations, and handlers live here with no rendering, so you can drive any framework from it.

Using React? @blazediff/react renders from this engine for you, with the same modes and props.

Features

  • Framework agnostic: drive the engine from React, Vue, Angular, Svelte, or vanilla JS
  • No web components: plain mount* functions and a subscribable engine
  • Multiple modes: swipe, two-up, onion skin, and difference visualization
  • Functional by default: the layout each mode needs is built in; classes are only for theming
  • Lightweight: zero dependencies except @blazediff/core

Quick Start

Every mode is a mount*(target, options) function. It appends the UI into target and returns a handle with update(options) and destroy().

import { mountSwipe } from "@blazediff/ui"; const handle = mountSwipe(document.getElementById("app"), { src1: "image1.jpg", src2: "image2.jpg", alt1: "Original", alt2: "Modified", onPositionChange: (position) => console.log(position), }); // later handle.update({ src2: "image3.jpg" }); handle.destroy();

Renderer API

mountSwipe(target, options)

Compare two images with a draggable divider.

OptionTypeDefaultDescription
src1string-URL of the first image
src2string-URL of the second image
alt1string"Before"Alt text for the first image
alt2string"After"Alt text for the second image
initialPositionnumber50Initial divider position (0–100)
classNamestring-Class for the root element
containerClassNamestring-Class for the container
image1ClassNamestring-Class for the first image
image2ClassNamestring-Class for the second image
dividerClassNamestring-Class for the divider
onPositionChange(position: number) => void-Called when the divider moves (0–100)

mountTwoUp(target, options)

Two images side by side, with dimension-change detection.

OptionTypeDefaultDescription
src1string-URL of the first image
src2string-URL of the second image
crossOriginstring | null"anonymous"crossOrigin for image loads
classNamestring-Class for the root element
containerClassNamestring-Class for the main container
containerInnerClassNamestring-Class for the inner container
panelClassNamestring-Class for each panel
imageClassNamestring-Class for images
dimensionInfoClassNamestring-Class for the dimension info text
onImagesLoaded(detail) => void-{ image1, image2 } dimensions
onLoadError(error: unknown) => void-Called when loading fails

mountOnionSkin(target, options)

Overlay two images with an opacity slider.

OptionTypeDefaultDescription
src1string-URL of the base image
src2string-URL of the overlay image
opacitynumber50Initial opacity (0–100)
crossOriginstring | null"anonymous"crossOrigin for image loads
classNamestring-Class for the root element
sliderLabelTextstring"Opacity:"Text for the slider label
containerClassNamestring-Class for the main container
imageContainerClassNamestring-Class for the image container
imageClassNamestring-Class for images
sliderContainerClassNamestring-Class for the slider container
sliderClassNamestring-Class for the slider
sliderLabelClassNamestring-Class for the slider label
onOpacityChange(opacity: number) => void-Called when opacity changes
onImagesLoaded(detail) => void-{ image1, image2 } dimensions
onLoadError(error: unknown) => void-Called when loading fails

mountDifference(target, options)

Show pixel differences using the BlazeDiff algorithm.

OptionTypeDefaultDescription
src1string-URL of the first image
src2string-URL of the second image
thresholdnumber0.1Difference threshold (0–1)
includeAAbooleanfalseInclude anti-aliased pixels
alphanumber0.1Opacity of the original
crossOriginstring | null"anonymous"crossOrigin for loads
classNamestring-Class for the root element
containerClassNamestring-Class for the container
canvasClassNamestring-Class for the canvas
onDiffComplete(detail) => void-{ diffCount, totalPixels, percentage }
onDiffError(error: unknown) => void-Called when comparison fails

Headless engine

Need another framework, or full control? Drive the engine directly from @blazediff/ui/engine. Each factory returns a controller with getState(), subscribe(listener), setConfig(config), actions, and destroy().

import { createSwipeEngine } from "@blazediff/ui/engine"; const engine = createSwipeEngine(50); const unsubscribe = engine.subscribe(() => { const { position, isDragging } = engine.getState(); // render position (0–100) however your framework wants }); engine.actions.start(40); // pass an already-computed percentage engine.actions.move(55); // clamped + ignored unless dragging engine.actions.end(); unsubscribe(); engine.destroy();
FactoryStateActions
createDifferenceEngine(config){ status, diff?: { output, width, height, diffCount, totalPixels, percentage }, error? }–
createSwipeEngine(initialPosition = 50){ position, isDragging }start, move, end, setPosition
createTwoUpEngine(config){ status, dims1, dims2, dimensionLabel, changed, error }–
createOnionSkinEngine(config, opacity = 50){ status, opacity, dims1, dims2, error }setOpacity

Also exported: formatDimensionLabel, normalizedOpacity, loadImageElement, getImageData, createStore.

The engine uses browser APIs (Image, a throwaway <canvas> for pixel extraction) but never touches the surface you render to β€” that boundary is what keeps it framework-agnostic.

Last updated on