Build a stencil with context

Call getCropperContext() during component initialization. Its stable cropper API is available immediately. Register a getter for stencil options before state-dependent markup; the cropper reads it before the first reset and tracks subsequent reactive changes.

<script lang="ts">
	import { MoveDiagonal } from '@lucide/svelte';
	import {
		DraggableArea,
		getCropperContext,
		StencilOverlay,
		StencilWrapper,
		type MoveDirections
	} from 'svelte-advanced-cropper';

	import { BoundingBoxType } from 'advanced-cropper/extensions/fit-to-image';

	const context = getCropperContext();
	const cropper = context.cropper;
	context.registerStencil(() => ({ aspectRatio: 1, boundingBox: BoundingBoxType.Circle }));
	const disabled = $derived(context.disabled);
	const state = $derived(cropper.getState());

	const coordinates = $derived(cropper.getStencilCoordinates());
	const transitions = $derived(cropper.getTransitions());

	function resize(shift: MoveDirections) {
		// Only the horizontal shift is used, on both axes: the square stencil grows evenly.
		cropper.resizeCoordinates('center', {
			left: shift.left,
			top: shift.left
		});
	}
</script>

{#if state}
	<StencilWrapper class="circle-stencil" {transitions} {...coordinates}>
		<DraggableArea
			{disabled}
			class="circle-stencil__handler"
			onMove={resize}
			onMoveEnd={cropper.resizeCoordinatesEnd}
		>
			<MoveDiagonal color="white" size={22} />
		</DraggableArea>
		<DraggableArea
			{disabled}
			class="circle-stencil__draggable-area"
			onMove={(directions) => cropper.moveCoordinates(directions)}
			onMoveEnd={cropper.moveCoordinatesEnd}
		>
			<StencilOverlay class="circle-stencil__overlay" />
		</DraggableArea>
	</StencilWrapper>
{/if}

<style>
	:global(.circle-stencil) {
		cursor: move;
	}
	:global(.circle-stencil__handler) {
		position: absolute;
		right: 15%;
		top: 14%;
		z-index: 1;
		cursor: ne-resize;
		width: 30px;
		height: 30px;
		display: flex;
		align-items: center;
		justify-content: center;
		transform: translate(50%, -50%);
	}
	:global(.circle-stencil__overlay) {
		border: dashed 2px white;
		box-sizing: border-box;
		border-radius: 50%;
	}
	:global(.circle-stencil__draggable-area) {
		width: 100%;
		height: 100%;
	}
</style>
  • RectangleStencil / CircleStencil child of Cropper or FixedCropper
    • getCropperContext() registerStencil · reactive cropper, disabled, image
        • line SimpleLine by default
          • LineWrapper → DraggableArea
        • handler SimpleHandler by default
          • HandlerWrapper → DraggableArea
            • StencilGrid grid prop
            • preview <div> __preview selector
          • children custom guides inside the crop region
Context Component Snippet
Wrapper
Preview Line Handler
Overlay Background image
Overlay
__overlay selector
Preview
__preview selector
Line
line snippet, lines prop
Handler
handler snippet, handlers prop
Stencil root
class, style; --moving, --resizing, --disabled state selectors
Guides
grid prop or a children snippet

Set props directly on the child stencil. Scope part selectors under its class: append the suffixes above to .advanced-cropper-rectangle-stencil or .advanced-cropper-circle-stencil.

Read reactive values

Use $derived(context.disabled), $derived(context.image) and $derived(context.cropper.getStencilCoordinates()). Reading a getter once into an ordinary constant takes a snapshot. image is the displayed image, including an image installed with setImage.

The effective disabled state is context.disabled || ownDisabled. A child cannot enable gestures disabled by its parent. Custom controls call the corresponding end method when their gesture finishes.

Constraint lifecycle

registerStencil(() => options) installs destruction cleanup itself and returns an idempotent cleanup for early removal. Options can include aspectRatio and boundingBox; a circle supplies both a square ratio and BoundingBoxType.Circle from the fit-to-image extension.

The cropper reconciles registration, removal, replacement and option changes even with autoReconcileState={false}. It does not reset the crop to defaults. More than one registration after an update settles is an error. Nested croppers isolate registrations; calling the getter outside a cropper throws.

Registration also works during SSR, without image loading, measurements or resets. Use the complete custom stencil tutorial for movement and resizing, and the context contract for exact types.