Viewport
Viewport takes the gestures, Image draws the photo in it and Mask dims everything outside the crop.
Anatomy
Viewport goes inside Root, with Image, Mask and Area inside it. Its width fills the parent like any block, so give it a height:
<Cropper.Viewport style={{ height: 400 }}>
<Cropper.Image />
<Cropper.Mask />
<Cropper.Area aria-label="Crop area" />
</Cropper.Viewport>One Viewport per cropper. A second one takes over the layout, and zriz warns in development. Draw a live preview with useCropperFrame.
zriz makes it position: relative when it is static and sets touch-action: none, so the page does not scroll under a finger. Image clips the photo to the box by itself. The stylesheet also clips the viewport, so a handle at its edge never hangs outside it.
The track in the demo above sets wheel to pan or zoom. The photo pans once you zoom in. The demo uses the styles from the zriz site.
Leave room around the crop
zriz fits the crop inside the viewport's padding. Without padding the crop touches the edges, and the clip cuts the handles in half. 24 px leaves room for the 44 px hit box to grab, see crop area:
[data-zriz-item="viewport"] {
padding: 24px;
}The crop fits again whenever the viewport changes size.
Put it in a dialog
Centre the dialog with inset: 0 and margin: auto. A percent translate on an odd size puts everything inside on a half pixel, and at 1x Chromium blurs the crop's outline.
Choose what the wheel does
A plain wheel or two-finger scroll pans the photo. wheel="zoom" makes it zoom, and wheel="none" leaves it to the page. Pinch and Ctrl + wheel zoom in every mode:
<Cropper.Viewport wheel="none" style={{ height: 400 }} />Turn off the two-finger twist
A twist straightens the photo once it passes 5°. To keep two fingers for pinch only:
<Cropper.Viewport
rotateGesture={false}
style={{ height: 400 }}
/>Fade the photo in when it loads
Image gets data-ready once the cropper knows the photo's size: when it decodes, or at once when you pass image to Root. Hide it until then:
[data-zriz-item="image"] {
transition: opacity 0.2s;
}
[data-zriz-item="image"]:not([data-ready]) {
opacity: 0;
}Darken the mask
The stylesheet paints the mask with --zriz-scrim. Set it to any colour:
[data-zriz-item="mask"] {
--zriz-scrim: rgb(0 0 0 / 0.7);
}What each gesture does
Viewport handles pointer, touch and wheel input with no extra code:
| Input | What it does |
|---|---|
| Drag | Pans the photo under the crop |
| Pinch, trackpad pinch | Zooms, with a rubber band at both ends |
| Twist two fingers | Straightens once the twist passes 5° |
| Wheel, trackpad scroll | Pans by default. At the edge the page scrolls on. wheel changes it |
| Ctrl + wheel | Zooms, whatever wheel is set to |
| Escape during a drag | Cancels the drag and puts the photo back |
A press on a button, link, input or other control inside Viewport starts no pan, so its click still lands. The same goes for content you portal out of it.
A twist counts as a pinch, so Root gets data-pinching and the thirds grid shows. A trackpad twist works only in Safari. The crop's keys are on crop area.
API
Viewport
Renders a <div> inside Root. Gesture attributes like data-panning go on Root. Every part also takes className, render and the props of its element, see composition. className and render get { interacting }.
- wheel "pan" | "zoom" | "none" = "pan"
- What a plain wheel or two-finger scroll does
- rotateGesture boolean = true
- Lets a two-finger twist straighten the photo
Image
Renders a <div> inside Viewport. It holds an <img> that moves while you drag and a <canvas> drawn sharp at rest. className and render get { ready }.
- alt string = ""
- Names the photo for screen readers. Leave it empty when the crop is described elsewhere
| Attribute | When |
|---|---|
| data-ready | Once the cropper knows the photo's size: after it decodes, or at once with image on Root |
| data-sharp | While the sharp canvas frame shows |
Mask
Renders an aria-hidden <div> inside Viewport. It has no props of its own. className and render get { shape }.
| Attribute | When |
|---|---|
| data-shape | "rect" or "circle", from Root |
| data-interacting | During a gesture and for a short hold after it, see accessibility |
| Variable | What it sets |
|---|---|
| --zriz-scrim | Paints the mask outside the crop, #000000c7 by default. Styling lists every colour |
The stylesheet fades it during a gesture, see styling.