← All @molecule/* packages · App templates
@molecule/app-image-crop-cropperjsProvider bond · image-crop · App (browser) · v1.0.1 · Apache-2.0
Headless crop-state provider for @molecule/app-image-crop (no canvas output — app draws the cropped pixels)
npm install @molecule/app-image-crop-cropperjsnpm · Source on GitHub · Implements @molecule/app-image-crop
@molecule/app-image-crop-cropperjs is a provider bond on the app (browser) side: it implements the image-crop core interface (@molecule/app-image-crop) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { provider } from '@molecule/app-image-crop-cropperjs'
import { setProvider, requireProvider } from '@molecule/app-image-crop'
setProvider(provider) // once, at app startup (bonds.ts)
const cropper = requireProvider().createCropper({ src: '/avatar.jpg', aspectRatio: 1 })
const canvas = cropper.getCroppedCanvas({ width: 200, height: 200 })
canvas.toBlob((blob) => uploadAvatar(blob), 'image/png')Works with: @molecule/app-image-crop
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Cropper.js image-crop provider for @molecule/app-image-crop — a REAL
implementation backed by cropperjs v1. createCropper({ src }) mounts a live
Cropper on an image element and every instance method delegates to the
corresponding cropperjs call, so getCroppedCanvas() returns the actual
cropped <canvas> (call .toBlob() / .toDataURL() on it to export) — not a
placeholder object.
import { provider } from '@molecule/app-image-crop-cropperjs'
import { setProvider, requireProvider } from '@molecule/app-image-crop'
setProvider(provider) // once, at app startup (bonds.ts)
const cropper = requireProvider().createCropper({ src: '/avatar.jpg', aspectRatio: 1 })
const canvas = cropper.getCroppedCanvas({ width: 200, height: 200 })
canvas.toBlob((blob) => uploadAvatar(blob), 'image/png')
provider
npm install @molecule/app-image-crop-cropperjs @molecule/app-image-crop cropperjs
npm install -D @types/cropperjs
CropperjsConfigProvider-level defaults applied to every cropper created by the provider.
These map onto cropperjs constructor options. Per-cropper CropperOptions
(e.g. guides) take precedence over the matching field here.
interface CropperjsConfig {
/**
* Whether to show the dashed crop guide lines by default. Overridden per-cropper
* by `CropperOptions.guides`. Defaults to `true`.
*/
guides?: boolean
/** Whether to render the checkerboard background behind the image. Defaults to `true`. */
background?: boolean
/**
* cropperjs view mode (0-3) constraining the crop box relative to the canvas /
* container. `1` restricts the crop box within the canvas. Defaults to `1`.
*/
viewMode?: 0 | 1 | 2 | 3
}
createProvider(config)Creates a Cropper.js-based image crop provider.
function createProvider(config?: CropperjsConfig): ImageCropProvider
config — Optional provider-level defaults (guides, background, viewMode).Returns: A configured ImageCropProvider backed by real cropperjs instances.
providerDefault Cropper.js provider instance.
const provider: ImageCropProvider
CropperImplements @molecule/app-image-crop interface.
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/app-image-crop'
import { provider } from '@molecule/app-image-crop-cropperjs'
export function setupImageCropCropperjs(): void {
setProvider(provider)
}
Peer dependencies:
@molecule/app-image-crop ^1.0.1@molecule/app-image-crop
cropperjs
Import cropperjs's stylesheet yourself — this package does NOT:
import 'cropperjs/dist/cropper.css'. Without it the crop box, handles, and
drag guides render unstyled (an invisible/broken cropper), the same way Quill
needs its theme CSS.
Browser-only. createCropper() calls document.createElement('img') and
new Cropper(...); construct it in a client-only effect under SSR.
cropperjs initializes on the image's load event. Reading getCropData()
or getCroppedCanvas() before the source has loaded returns empty/degenerate
data — drive them after load, or set the region explicitly with setCropData()
in natural-image coordinates first.
circular is a UI concern, not a pixel op. cropperjs has no circular
pixel output; for a round avatar, style the crop box round in CSS
(.cropper-view-box, .cropper-face { border-radius: 50% }) and draw the
returned canvas into a rounded canvas before upload.
OutputOptions.quality applies at encode time, not to canvas generation —
pass it to canvas.toBlob(cb, 'image/jpeg', quality) / toDataURL(type, quality).
Provider defaults (guides, background, viewMode) come from
createProvider(config); per-cropper CropperOptions.guides wins over the
config default. maxWidth/maxHeight are enforced on output (cropperjs has no
max-crop-box constructor option).
Wire it with setProvider() from @molecule/app-image-crop or
bond('image-crop', provider) from @molecule/app-bond — both route through
the shared registry; requireProvider() throws until one has run.
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual crop screen (avatar/photo upload, etc.), and check every box off one by one. A box you can't check is an integration bug to fix — not a skip. The cropper is headless, so verify the UI YOU rendered:
createCropper({ src }))
and renders it with a visible crop-box overlay + drag handles drawn from
getCropData() — not a blank or full-bleed image with no selection.setCropData(),
getCropData() reflects the new x/y/width/height, and the on-page preview
(the getCroppedCanvas() output drawn into the page) updates to show ONLY
the selected area, not the whole image.aspectRatio: 1 for an avatar) the crop
box stays that ratio while you resize — getCropData() width == height for
1:1 — and circular: true clips the preview to a circle.rotate() / zoom() transform the source and the crop overlay follows:
getCropData().rotate / scaleX change and the preview re-renders the
transformed region — the selection isn't stranded on the old orientation.getCroppedCanvas() pixels
match the selected region (not the full source), and downstream the SAVED
file is the cropped Blob (canvas.toBlob → upload) — re-fetch and render the
stored image and confirm it shows the crop, never the original.minWidth/minHeight or larger than maxWidth/maxHeight.