Svelte / Vue / Solid
No framework component yet — use @ditherkit/core directly with about 20 lines of glue.
Status: 🛠 Use core directly. No framework wrapper package exists or is planned.
@ditherkit/corehas no framework dependencies and works anywhere — this page shows how to wire it into Svelte, Vue, and Solid.
The @ditherkit/core algorithms are pure functions that take a
Uint8ClampedArray of RGBA pixels. That means any framework
can use them — all you need is a way to:
- Load an image into a canvas
- Extract
ImageDatafrom the canvas - Call the dithering functions
- Put the result back into the canvas
That's ~20 lines of glue code, and the glue doesn't care which framework you're in. The same pattern works in Svelte, Vue, Solid, Qwik, and any other framework. This page shows Svelte explicitly; the Vue and Solid versions are structurally identical.
What works
@ditherkit/core— all seven algorithms (Floyd-Steinberg, Atkinson, Jarvis-Judice-Ninke, Stucki, Burkes, Bayer, Threshold) plus colour/image helpers.- Canvas rendering — every framework supports refs/bindings to DOM elements, which is all you need.
- Web Worker offloading — you can write your own tiny worker
that calls into
@ditherkit/coreif you need to keep the main thread free. It's ~30 lines.
What doesn't work
- No
<DitheredImage />Svelte/Vue/Solid component. You write your own, usually in less time than it would take to install a wrapper package. - No shared worker singleton. The
@ditherkit/reactworker management is React-specific. You'd write your own or just create a worker per component instance. - No server-side caching. Same as React SPA — there's no server to cache against. Use build-time dithering (like the Astro Pattern 2) or a small backend.
Quick start — Svelte
pnpm add @ditherkit/core<!-- src/lib/DitheredImage.svelte -->
<script lang="ts">
import { onMount } from 'svelte'
import {
floydSteinbergDither,
grayscale,
type Color,
} from '@ditherkit/core'
export let src: string
export let width: number
export let height: number
export let algorithm: 'Floyd-Steinberg' = 'Floyd-Steinberg'
let canvas: HTMLCanvasElement
const palette: Color[] = [
{ r: 0, g: 0, b: 0 },
{ r: 255, g: 255, b: 255 },
]
onMount(async () => {
const image = new Image()
image.crossOrigin = 'Anonymous'
await new Promise((resolve, reject) => {
image.onload = resolve
image.onerror = reject
image.src = src
})
const ctx = canvas.getContext('2d')!
ctx.drawImage(image, 0, 0, width, height)
const imageData = ctx.getImageData(0, 0, width, height)
grayscale(imageData.data)
floydSteinbergDither(imageData.data, width, height, palette)
ctx.putImageData(imageData, 0, 0)
})
</script>
<canvas bind:this={canvas} {width} {height}></canvas>Use it:
<script>
import DitheredImage from '$lib/DitheredImage.svelte'
</script>
<DitheredImage src="/portrait.jpg" width={400} height={600} />Same shape works in Vue (with <script setup> and a <canvas ref>)
and Solid (with createEffect and a <canvas ref>).
Adding a Web Worker (optional)
The component above runs dithering on the main thread, which will block the UI for a few milliseconds on small images and longer on large ones. To offload to a worker:
// src/lib/dither.worker.ts
import {
floydSteinbergDither,
grayscale,
type Color,
} from '@ditherkit/core'
const palette: Color[] = [
{ r: 0, g: 0, b: 0 },
{ r: 255, g: 255, b: 255 },
]
self.onmessage = (e) => {
const { imageData, width, height } = e.data
grayscale(imageData.data)
floydSteinbergDither(imageData.data, width, height, palette)
self.postMessage({ imageData }, [imageData.data.buffer])
}Then in your component:
const worker = new Worker(
new URL('./dither.worker.ts', import.meta.url),
{ type: 'module' }
)
worker.postMessage({ imageData, width, height })
worker.onmessage = (e) => {
ctx.putImageData(e.data.imageData, 0, 0)
worker.terminate()
}This is roughly what @ditherkit/react does internally, minus the
shared-singleton optimisation and the AbortSignal cancellation.
For most use cases, the simpler per-component worker is fine.
Vue variant
Same pattern, idiomatic Vue:
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { floydSteinbergDither, grayscale, type Color } from '@ditherkit/core'
const props = defineProps<{ src: string; width: number; height: number }>()
const canvas = ref<HTMLCanvasElement | null>(null)
const palette: Color[] = [
{ r: 0, g: 0, b: 0 },
{ r: 255, g: 255, b: 255 },
]
onMounted(async () => {
if (!canvas.value) return
const image = new Image()
image.crossOrigin = 'Anonymous'
await new Promise((resolve) => {
image.onload = resolve
image.src = props.src
})
const ctx = canvas.value.getContext('2d')!
ctx.drawImage(image, 0, 0, props.width, props.height)
const imageData = ctx.getImageData(0, 0, props.width, props.height)
grayscale(imageData.data)
floydSteinbergDither(imageData.data, props.width, props.height, palette)
ctx.putImageData(imageData, 0, 0)
})
</script>
<template>
<canvas ref="canvas" :width="width" :height="height"></canvas>
</template>Solid variant
import { onMount, createSignal } from 'solid-js'
import { floydSteinbergDither, grayscale, type Color } from '@ditherkit/core'
export function DitheredImage(props: {
src: string
width: number
height: number
}) {
let canvas: HTMLCanvasElement | undefined
const palette: Color[] = [
{ r: 0, g: 0, b: 0 },
{ r: 255, g: 255, b: 255 },
]
onMount(async () => {
if (!canvas) return
const image = new Image()
image.crossOrigin = 'Anonymous'
await new Promise((resolve) => {
image.onload = resolve
image.src = props.src
})
const ctx = canvas.getContext('2d')!
ctx.drawImage(image, 0, 0, props.width, props.height)
const imageData = ctx.getImageData(0, 0, props.width, props.height)
grayscale(imageData.data)
floydSteinbergDither(imageData.data, props.width, props.height, palette)
ctx.putImageData(imageData, 0, 0)
})
return <canvas ref={canvas} width={props.width} height={props.height} />
}Will there ever be framework-specific packages?
Not planned. The glue code is short enough that maintaining four more packages would cost more than it saves. If you write a nice reusable component in your own codebase, consider publishing it as a community package — we'd link to it from here.
Related docs
@ditherkit/core— the full algorithm reference- Image adjustments — grayscale, brightness, contrast
- How it works — how
@ditherkit/reactdoes it, for inspiration