Methods

Every method on a Kiri instance.

Kiri

MethodDescription
new Kiri(container, options?)Constructs a cropper inside container, which must already exist in the DOM. Throws a clear error otherwise. See Settings for every option.
load(source, options?)Loads a File, Blob, or URL string. Returns a Promise<void> that resolves once the image has decoded. options: initial zoom, offset, rotation, flip (zoom/offset are ignored when movableFrame: true — the image always loads centered at its fixed size).
getState()Returns a snapshot: { zoom, offset, rotation, flip, filters, framePosition }. A copy — mutating the returned object has no effect. framePosition is only meaningful when movableFrame: true (always {0,0} otherwise).
setZoom(zoom)Absolute zoom, clamped to [minZoom, maxZoom]. No-op if movableFrame: true — the image never zooms in that mode.
setOffset(offset)Absolute pan — { x, y }, the image's center offset from the frame center in stage pixels. Clamped so the frame stays fully covered by the image. No-op if movableFrame: true; see setFramePosition() instead.
reset()Reverts zoom/offset/rotation/flip/filters/framePosition to whatever they were right after load() resolved (including any loadOptions passed to it). No-op before anything has been loaded.
rotate(deltaDeg)Relative rotation, snapped to the nearest 90°. No-op if rotatable: false.
flipHorizontal() / flipVertical()Toggles. No-op if flippable: false.
setFrameSize(width, height)Resizes the frame (and the stage too, if autoSizeStage — except in movableFrame mode, where the stage stays pinned to the fixed image's own size instead of following the frame). Clamped to a 20px minimum per axis, and to the fixed image's own size when movableFrame: true (no auto-zoom left to grow into).
setFramePosition(position)Only meaningful when movableFrame: true (otherwise a no-op). Absolute frame position — { x, y } over the fixed image, clamped so the frame stays fully within the image's bounds. See Settings.
setFilters(partial)Merges into the current filters; numeric values clamped to >= 0. See Filters.
export(options?)Renders the current crop. Returns Promise<string | Blob | HTMLCanvasElement> depending on type. See Settings.
getCropRegion()Returns { x, y, width, height, rotation, flip } — the current crop selection as a rectangle in the original, unrotated, unflipped source image's own pixel coordinates. For sending to a server that crops the full-resolution original itself, instead of uploading a client-re-encoded image. The server reproduces the transform by cropping to x/y/width/height first, then rotating, then flipping, in that order — matching how Kiri itself composes them. See the crop region example.
upload(url, options?)Exports as a blob, then uploads it — a default FormData/fetch POST, or a custom uploader.
on("change", callback) / off("change", callback)Subscribe/unsubscribe to state changes. See Events.
destroy()Removes all event listeners (drag/zoom gestures, resize handles, zoom slider, keyboard) and clears the container's innerHTML, leaving an empty container element. Call this when you're done with an instance (e.g. unmounting a component).

The stage itself is keyboard-accessible once focused (tab to it, or click it): arrow keys pan via setOffset(), +/- zoom via setZoom(), and 0 calls reset().

Batch cropping

Stepping one shared Kiri instance through a queue of images isn't a separate class — it's a small recipe built entirely on the methods above (mainly load() and export()) plus a plain array for the queue. One DOM/stage instance is reused across images, not one instance per image, so every interaction you already know (drag, zoom, rotate, flip, filters) works exactly the same, with no separate API to learn.

import { Kiri } from "@michaelyagi/kiri";

const items = [];   // { source, loadOptions? }
let index = -1;
const captures = [];

const cropper = new Kiri(container, options);

async function next() {
  if (index + 1 >= items.length) return false;
  index += 1;
  await cropper.load(items[index].source, items[index].loadOptions);
  return true;
}

async function previous() {
  if (index <= 0) return false;
  index -= 1;
  await cropper.load(items[index].source, items[index].loadOptions);
  return true;
}

async function capture(exportOptions) {
  const result = await cropper.export(exportOptions);
  captures[index] = result;
  return result;
}
items.push({ source: fileA }, { source: fileB });

while (await next()) {
  // cropper now shows items[index].source — let the user adjust it
  // (drag/zoom/rotate/whatever), then capture it:
  await capture();
}

captures; // all crops, in order
cropper.destroy();

You don't have to build the whole queue up front — it's just an array, so push to it whenever, e.g. from a file input's change event: for (const file of fileInput.files) items.push({ source: file });. previous() mirrors next() — it steps cropper back one item and returns false (without moving) once index is already 0, so a "Back" button can disable itself the same way a "Next" button does at the end of the queue.

See a working version on the batch cropping example — it's this exact recipe, copy-pasted into a real page.

Next: Events — what fires and when.