Documentation
Canvas (experimental)

Canvas

⚠️

Experimental. Canvas is Clover's own 2D renderer, under development as a possible replacement for OpenSeadragon and Vidstack. Its API may change in any release. Image and the Viewer's default path are unaffected.

A pan and zoom surface for IIIF images, video and sound, drawn with WebGL2 and falling back to Canvas2D. Everything on a IIIF Canvas shares one camera, so it moves together.

Loading…

The painting body from the IIIF Cookbook's Image Service recipe (opens in a new tab), tiled from its Image API 3.0 service.

Image API 2.1.1 and 2.0

The same image from the reference server's 2.1 and 2.0 endpoints, given as src with isTiledImage, and a Northwestern level 2 service that lists its own tiles.

Loading…
Loading…
Loading…

Annotations

Rectangular (xywh) annotation targets become hotspots, as in Image: hover or focus one to see its label, and click it (or press Enter) to zoom to it. The comment control in the cluster lists them by their text; picking one zooms to it and keeps its hotspot highlighted.

Loading…

Metadata

A Canvas with a summary or metadata gets an information control (an i) in the cluster. It slides out a box over the picture with each shown Canvas's label, summary and metadata, as the <figcaption> of the Canvas's <figure>. The box stays open while you pan and zoom, and as the host moves to another Canvas that has information to show. Close it from its own button, the i control, or Escape. Here each Canvas describes itself (0029 (opens in a new tab)).

IIIF Canvases

Canvas draws IIIF Canvases: pass one, or the few you want shown together, as canvases. Each Canvas's painting annotations are composed at their targets, and several Canvases are laid out in reading order by viewingDirection.

Canvas knows nothing of the Manifest around those Canvases. Deciding what to show — reading behavior to group a Manifest into single pages, spreads or one joined object — and stepping between views is the host's job, as it is the Viewer's. Each example below is a small host doing exactly that for a IIIF Cookbook recipe, with its previous / next below the Canvas rather than inside it. The grouping rules it uses are Clover's own (groupViews in src/lib/iiif-sequence.ts), the same ones the Viewer follows.

individuals

One Canvas at a time — the spec's default. (0011 (opens in a new tab))

paged

Spreads, two Canvases at a time, after a cover that stands alone. (0009 (opens in a new tab))

paged, with a non-paged Canvas

A Canvas marked non-paged is never paired — here, a foldout photographed unfolded — and pairing carries on after it. (0035 (opens in a new tab))

paged, with a facing-pages Canvas

A Canvas marked facing-pages is already a spread, so it is shown alone. No recipe has one, so this marks the third Canvas of the book above facing-pages.

continuous

One object in several Canvases — a scroll photographed in sections — joined edge to edge in a single view. (0011 (opens in a new tab))

right-to-left

The recipe shows its Canvases individually, so "next" is the button on the left. As spreads (behavior="paged"), each spread's first page is on the right. (0010 (opens in a new tab))

top-to-bottom

Individually, the buttons point up and down. Joined (behavior="continuous"), the Canvases stack downward. (0010 (opens in a new tab))

bottom-to-top

No recipe reads upward, so this is the one above with viewingDirection="bottom-to-top": the first Canvas at the bottom, the stack growing up.

Several images on one Canvas

Composition

Each painting annotation's target places its image on the Canvas: here a detail, photographed separately, is inset over the page it comes from. (0036 (opens in a new tab))

Choice

A Choice offers alternative images of the same thing. The first is shown; the bullet control in the cluster lists the others to switch to, keeping your place. (0033 (opens in a new tab))

A Canvas larger than its image

World coordinates are the Canvas's: a 640 × 360 image painted on a 1920 × 1080 Canvas is stretched to fill it, and annotations on the Canvas land where they should. (0004 (opens in a new tab))

Video and sound

A time-based Canvas plays in Clover's own transport: play, a seek bar, volume, time and captions, styled as the Viewer's Player. On its own, a video is fitted to the stage and stays put. Beside other Canvases, it is placed in the scene like an image and moves with them. The media code loads only when a Canvas paints video or sound, so a page of images never downloads it.

Video

(0003 (opens in a new tab))

Captions

A supplementing annotation with a WebVTT body becomes a caption track. The first is shown by default; the captions control switches tracks or turns them off. Clover draws the cues itself, so they stay a readable size at any zoom. (0219 (opens in a new tab))

A Choice of caption files offers each language. (0074 (opens in a new tab))

Poster from a placeholder Canvas

The placeholderCanvas's image is shown until playback starts. (0013 (opens in a new tab))

Video drawn into the canvas

By default a video is a real <video> element positioned by the camera. The browser composites it, so picture-in-picture, AirPlay and HDR keep working. With options.media.presentation: "texture", each frame is drawn into the WebGL canvas instead, in paint order with the images around it. Frames upload only as the video decodes them. This needs CORS on the media: without it, that video falls back to dom.

Sound

Sound plays through the same transport. With no picture of its own, the Canvas is a 16:9 stage. (0002 (opens in a new tab))

Sound with an accompanying Canvas

The accompanyingCanvas (else the placeholderCanvas) is shown while it plays. (0014 (opens in a new tab))

Features

Canvas covers what Image does, apart from OpenSeadragon's own configuration:

  • IIIF Image API 3.0, 2.1.1 and 2.0. Each service is tiled from its info.json:

    • from its tiles when it lists them
    • from its sizes when it is a level 0 tree that lists only sizes
    • from tiles Clover cuts itself when it is level 1 or 2 and lists none

    Requests use each version's canonical spelling, which is all a static level 0 tree answers to. A body whose service cannot be reached falls back to its id.

  • Lazy loading. Nothing is fetched until a Canvas comes within half a screen of view: no info.json or tiles, and no video, sound or captions. A page of many Canvases costs nothing until it is scrolled, and a hidden one waits until it is shown.

  • Deep zoom. It loads the coarsest level first, then the level that fits the screen, nearest the centre first. Tiles that scroll out of view are cancelled. Tiles stay cached up to a memory budget.

  • Several images (body or src arrays) are laid out side by side at a common height, and body.region (x,y,w,h or pct:x,y,w,h) shows only that part of an image.

  • Annotations with rectangular targets become hotspots.

  • Controls: the same cluster as Image, including controlButtons replacements.

  • Full screen and rotation, and a navigator (an overview, off by default: navigator turns it on).

  • An annotations menu, from a comment control, when there are annotations.

  • A caption of each Canvas's summary and metadata, from an information control.

  • No CORS needed. Images served without CORS are still shown: WebGL may not read them, so they are placed as real <img> elements by the same camera.

  • Video and sound (Video and Sound painting bodies, HLS included) play in Clover's own transport, with WebVTT captions, a placeholder poster, and an accompanying image for sound.

Interaction

The controls, navigator, a video or sound's transport, and a soft scrim behind them appear while the pointer moves over the Canvas or focus is anywhere inside it. They fade after hideDelay, or a second after the pointer or keyboard focus leaves, as the Viewer's Player does. Chrome shown by keyboard focus stays while focus is inside.

InputAction
DragPan, with momentum on release
ClickZoom in (Shift-click: out)
PinchZoom about the pinch
Trackpad pinchZoom about the pointer
WheelScrolls the page, unless scrollToZoom
Double tap (touch)Zoom in
Arrow keysPan (Shift: further)
+ / -Zoom in / out
0Home, upright
r / RRotate 90° clockwise / anticlockwise
NavigatorPress or drag to move there

A single video or sound Canvas is a player rather than a picture to explore. It is always fitted to the stage, at the video's own aspect, and does not pan, zoom or rotate. Its only chrome is the transport at the bottom, which carries full screen; there are no top controls or scrim, unless it has a Choice to pick or information to read. A click on the picture plays and pauses, and the focused Canvas takes these keys:

InputAction
Space / kPlay / pause
j / lBack / forward ten seconds
mMute

Usage

import Canvas from "@samvera/clover-iiif/canvas";
 
<Canvas
  src="https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen"
  isTiledImage
  label="Göttingen market square"
  onReady={(canvas) => canvas.zoomBy(2)}
  onViewportChange={(bounds) => console.log(bounds)}
/>;

Props

PropTypeDescription
canvasesIIIF Canvas objectsOne Canvas, or several shown together (a spread).
viewingDirection"left-to-right" and so onHow to lay out several canvases.
bodyIIIF painting body, or an arrayImage resources to draw. Takes precedence over src.
srcstring | string[]Image URLs, or image service URIs with isTiledImage.
isTiledImagebooleanTreat each src as an image service (base URI or info.json).
labelInternationalString | stringAccessible name for the viewport.
instanceIdstringA new id remounts the renderer.
annotations{ annotation, targetIndex }[]Hotspots, as Image takes them. Rectangular targets only.
onAnnotationActive(id: string | null) => voidThe hovered or focused hotspot.
navigatorbooleanShow the overview in the top left. Default false.
controlsboolean | { zoom, fullPage, rotation, reset, annotations, information }Which controls to show. Default all.
controlButtonsControlButtonsReplace any control, as the Viewer's controlButtons.
optionsCloverCanvasOptionsSee below.
onReady(handle: CanvasHandle) => voidCalled once the first images are laid out and fitted.
onViewportChange(bounds: Rect) => voidThe visible region, in image coordinates, at most once per frame.
onMediaElement(element: HTMLVideoElement | null) => voidA time-based Canvas's media element, for a host's own transcript.
onEnded() => voidPlayback reached the end: a host's cue to advance.

options

OptionDefaultDescription
backend"auto""auto", "webgl2" or "canvas2d".
gesturesclickToZoom, dblClickToZoom, pinchToZoom, scrollToZoom (false).
maxZoomPixelRatio1.1Largest zoom, in CSS pixels per image pixel.
minZoomImageRatio0.9Smallest zoom, as a fraction of the home zoom.
animationTime0.09Easing time constant in seconds. 0 under reduced motion.
hideDelay2000Milliseconds the controls and navigator stay after the pointer stops, as the Player's.
withCredentialsfalseSend cookies with info.json and image requests.
requestHeadersExtra headers for those requests (these force a CORS preflight).
media.presentation"dom""dom" places a real <video>; "texture" draws its frames into the canvas (CORS).

CanvasHandle

Coordinates are world units, which are the first image's own coordinates (its declared width/height). That makes getBounds() an xywh= region as it stands.

fitBounds(rect), fitItemRect(index, rect), itemRectToWorld(index, rect), getBounds(), getHomeBounds(), home(), zoomBy(factor, at?), panBy(dx, dy), setRotation(degrees), rotateBy(degrees), getRotation(), getCamera(), worldToScreenPoint(point), screenToWorldPoint(point), and on(event, listener) for "viewport", "change", "error" and "backend".