Absolute zoom
A zoom button applies a relative factor. A slider needs an absolute value that stays in sync after wheel and touch gestures. This tutorial combines a fixed cropper, a wrapper snippet and the core’s absolute-zoom extension.
Compose the fixed frame
CustomCropper wraps FixedCropper, forwards its typed props, and uses an inert rectangle unless the caller supplies children. It exposes the inner instance through an application-owned bindable cropper prop. Its wrapper snippet renders the custom toolbar wrapper.
<script lang="ts">
import {
FixedCropper,
RectangleStencil,
ImageRestriction,
type FixedCropperProps,
type FixedCropperInstance
} from 'svelte-advanced-cropper';
import CustomWrapper from './CustomWrapper.svelte';
interface Props extends FixedCropperProps {
/** The inner cropper, for `bind:cropper`. */
cropper?: FixedCropperInstance;
}
let { children, wrapper: customWrapper, cropper = $bindable(), ...props }: Props = $props();
</script>
<FixedCropper bind:this={cropper} imageRestriction={ImageRestriction.stencil} {...props}>
{#snippet wrapper(p)}
{#if customWrapper}{@render customWrapper(p)}{:else}<CustomWrapper {...p} />{/if}
{/snippet}
{#if children}{@render children()}{:else}
<RectangleStencil handlers={false} lines={false} movable={false} resizable={false} />
{/if}
</FixedCropper>Read zoom with derived state
The core extension advanced-cropper/extensions/absolute-zoom provides getAbsoluteZoom(state, settings) and getZoomFactor(state, settings, value). Add advanced-cropper as a direct dependency when importing extension subpaths in an application using a strict package manager.
The normalized absolute value runs from 0 at the minimum allowed zoom to 1 at the maximum. Restrictions and stencil size affect that range, so a separate local slider value would drift from the actual cropper.
The wrapper reads state and settings with $derived, then derives absolute zoom. getZoomFactor converts a requested absolute value back to the relative factor accepted by zoomImage.
<script lang="ts">
import {
CropperFade,
type CropperWrapperSnippetProps,
type FixedCropperSettings
} from 'svelte-advanced-cropper';
import { getAbsoluteZoom, getZoomFactor } from 'advanced-cropper/extensions/absolute-zoom';
import Navigation from './Navigation.svelte';
type Props = CropperWrapperSnippetProps<FixedCropperSettings>;
let { cropper, class: cssClass, style, children }: Props = $props();
const state = $derived(cropper.getState());
const settings = $derived(cropper.getSettings());
const absoluteZoom = $derived(getAbsoluteZoom(state, settings));
// Fade in once the image has loaded and the cropper has a state for it.
const visible = $derived(state !== null && cropper.isLoaded());
function onZoom(value: number, transitions = false) {
cropper.zoomImage(getZoomFactor(state, settings, value), { transitions });
}
</script>
<CropperFade class={['custom-wrapper', cssClass]} {style} {visible}>
{@render children?.()}
<Navigation class="custom-wrapper__navigation" zoom={absoluteZoom} {onZoom} />
</CropperFade>
<style>
:global(.custom-wrapper) {
flex-grow: 1;
min-height: 0;
}
/* The fade is the cropper's root element here. Animate only the fade itself, so a
size change (e.g. `max-height` arriving with a late stylesheet) applies at once
instead of being measured halfway through a transition. */
:global(.custom-wrapper.advanced-cropper-fade) {
transition-property: opacity, visibility;
}
:global(.custom-wrapper__navigation) {
max-width: 100%;
width: 462px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
}
</style>Keep the wrapper contract
The wrapper is typed with CropperWrapperSnippetProps<FixedCropperSettings>. Forward class and style, and render children even while the image is loading so the stencil can initialize. CropperFade hides the contents visually without removing them.
Limit fade transitions to opacity and visibility when that element also controls layout size. Otherwise a late height change can be measured halfway through an animation.
Connect the controls
The navigation calls onZoom. Slider movement uses no transitions so the image follows the pointer; button steps can animate. The source panel includes the keyboard and pointer controls used by the example.
See layer snippets to apply the same toolbar pattern to a movable cropper.