Skip to navigation

Zoom

A zoom slider and step buttons, for people with no wheel or pinch.

Open
Download
1.0

Anatomy

Put ZoomThumb inside Zoom. The steps go anywhere inside Root:

<Cropper.ZoomStep factor={0.8} aria-label="Zoom out">
  −
</Cropper.ZoomStep>
<Cropper.Zoom
  aria-label="Zoom"
  style={{ width: 240, height: 24 }}
>
  <Cropper.ZoomThumb />
</Cropper.Zoom>
<Cropper.ZoomStep factor={1.25} aria-label="Zoom in">
  +
</Cropper.ZoomStep>

zriz paints nothing here and gives these parts no size. Give Zoom a width and a height, then style the track and the thumb. A press on the track jumps the zoom to that point. Full editor paints a track and a thumb you can copy.

The demo above stops the zoom at 3× with maxZoom on Root, marks a step at the limit as shown below and uses the styles from the zriz site.

Centre the thumb

zriz sets the thumb's inset-inline-start on a log scale, so 2× sits 26% along a 15× track. This centres a round thumb on that point:

[data-zriz-item="zoom-thumb"] {
  top: 4px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  translate: -50% 0;
  background: #111;
}
@media (forced-colors: active) {
  [data-zriz-item="zoom-thumb"] {
    background: CanvasText;
  }
}

In a right-to-left layout, use translate: 50% 0. Forced colours mode drops a plain background, so the second rule paints the thumb in CanvasText.

Stop the slider at 5×

Pass the same max to Zoom and ZoomThumb. Pinch still reaches 15×:

<Cropper.Zoom
  max={5}
  aria-label="Zoom"
  style={{ width: 240, height: 24 }}
>
  <Cropper.ZoomThumb max={5} />
</Cropper.Zoom>

To cap every way of zooming, set maxZoom on Root instead. Both parts follow it.

Mark a step at the limit

At 1× and at maxZoom, a press does nothing, but the button stays enabled. A large minSize can stop the zoom below maxZoom, and useZoom does not report that stop, so this recipe does not mark it. useZoom tells you where the zoom is. Use aria-disabled for this, because disabled drops keyboard focus from the button at the limit. Render this inside Root:

"use client";

import { useZoom } from "@zriz/react";
import * as Cropper from "@zriz/react/cropper";

export const ZoomSteps = () => {
  const zoom = useZoom();

  return (
    <>
      <Cropper.ZoomStep
        factor={0.8}
        aria-label="Zoom out"
        aria-disabled={zoom.value <= zoom.min || undefined}
      >
        −
      </Cropper.ZoomStep>
      <Cropper.ZoomStep
        factor={1.25}
        aria-label="Zoom in"
        aria-disabled={zoom.value >= zoom.max || undefined}
      >
        +
      </Cropper.ZoomStep>
    </>
  );
};

The browser dims only disabled, so paint the off state yourself:

[data-zriz-item="zoom-step"][aria-disabled="true"] {
  opacity: 0.4;
}

Build your own slider

useZoom hands a slider of your own the zoom and a commit callback. This one is a range input. Render it inside Root:

"use client";

import { useZoom } from "@zriz/react";

export const ZoomSlider = () => {
  const zoom = useZoom();

  return (
    <input
      type="range"
      aria-label="Zoom"
      min={zoom.min}
      max={zoom.max}
      step={0.01}
      value={zoom.value}
      onChange={(e) =>
        zoom.onValueChange(e.target.valueAsNumber)
      }
      onPointerUp={zoom.onValueCommitted}
      onPointerCancel={zoom.onValueCommitted}
      onKeyUp={zoom.onValueCommitted}
    />
  );
};

A range input moves in even steps, so 2× sits 7% along a 15× track. Zoom uses a log scale.

Hooks lists every field useZoom returns.

Keyboard

These keys work while Zoom has focus:

KeyWhat it does
leftorrightMove 1/20 of the track. Right zooms in, left in RTL
upordownMove 1/20 of the track. Up zooms in
shift+arrow keysMove half the track
page uporpage downThe same as Shift and an arrow key
homeorendGo to 1× or to max

Each step multiplies the zoom by the same amount, about 1.14× on a 15× track.

Escape during a drag cancels it and puts the zoom back.

API

Zoom

Renders a <div role="slider"> anywhere inside Root. Every part also takes className, render and the props of its element, see composition. className and render get { dragging }.

Prop
aria-label string
Names the slider for screen readers. Development builds warn without it
max number
The zoom at the end of the track. Defaults to maxZoom on Root
format Intl.NumberFormatOptions
How screen readers hear the zoom. By default 2 reads as "200%". An aria-valuetext of yours wins over it
AttributeWhen
data-draggingWhile a pointer holds any Zoom track of this cropper or a useZoom slider moves, so a second copy lights up too

ZoomThumb

Renders an aria-hidden <div> inside Zoom. zriz writes position: absolute and inset-inline-start, from 0% at 1× to 100% at max.

Prop
max number
The zoom at the end of the track. Defaults to maxZoom on Root. Keep it equal to max on Zoom

ZoomStep

Renders a <button type="button"> anywhere inside Root. Each press jumps at once, with no animation and no grid.

Prop
factor number Required
Multiplies the zoom on each press. Below 1 zooms out, so 0.8 undoes 1.25