# Viewport

> [llms.txt](https://zriz.dev/llms.txt)

`Viewport` takes the gestures, `Image` draws the photo in it and `Mask` dims everything outside the crop.

The page at https\://zriz.dev/docs/viewport runs this cropper: a 3:2 photo, a track that sets the wheel to pan or zoom, and open, reset and download in the corners.

## 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:

```tsx
<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](https://zriz.dev/docs/crop-area.md):

```css
[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:

```tsx
<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:

```tsx
<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:

```css
[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:

```css
[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](https://zriz.dev/docs/crop-area.md).

## 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](https://zriz.dev/docs/composition.md). `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](https://zriz.dev/docs/accessibility.md) |

| Variable | What it sets |
| - | - |
| `--zriz-scrim` | Paints the mask outside the crop, `#000000c7` by default. [Styling](https://zriz.dev/docs/styling.md) lists every colour |

The stylesheet fades it during a gesture, see [styling](https://zriz.dev/docs/styling.md).
