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.
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.
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.
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
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
tileswhen it lists them - from its
sizeswhen 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. - from its
-
Lazy loading. Nothing is fetched until a Canvas comes within half a screen of view: no
info.jsonor 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 (
bodyorsrcarrays) are laid out side by side at a common height, andbody.region(x,y,w,horpct:x,y,w,h) shows only that part of an image. -
Annotations with rectangular targets become hotspots.
-
Controls: the same cluster as
Image, includingcontrolButtonsreplacements. -
Full screen and rotation, and a navigator (an overview, off by default:
navigatorturns it on). -
An annotations menu, from a comment control, when there are annotations.
-
A caption of each Canvas's
summaryandmetadata, 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 (
VideoandSoundpainting 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.
| Input | Action |
|---|---|
| Drag | Pan, with momentum on release |
| Click | Zoom in (Shift-click: out) |
| Pinch | Zoom about the pinch |
| Trackpad pinch | Zoom about the pointer |
| Wheel | Scrolls the page, unless scrollToZoom |
| Double tap (touch) | Zoom in |
| Arrow keys | Pan (Shift: further) |
| + / - | Zoom in / out |
| 0 | Home, upright |
| r / R | Rotate 90° clockwise / anticlockwise |
| Navigator | Press 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:
| Input | Action |
|---|---|
| Space / k | Play / pause |
| j / l | Back / forward ten seconds |
| m | Mute |
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
| Prop | Type | Description |
|---|---|---|
canvases | IIIF Canvas objects | One Canvas, or several shown together (a spread). |
viewingDirection | "left-to-right" and so on | How to lay out several canvases. |
body | IIIF painting body, or an array | Image resources to draw. Takes precedence over src. |
src | string | string[] | Image URLs, or image service URIs with isTiledImage. |
isTiledImage | boolean | Treat each src as an image service (base URI or info.json). |
label | InternationalString | string | Accessible name for the viewport. |
instanceId | string | A new id remounts the renderer. |
annotations | { annotation, targetIndex }[] | Hotspots, as Image takes them. Rectangular targets only. |
onAnnotationActive | (id: string | null) => void | The hovered or focused hotspot. |
navigator | boolean | Show the overview in the top left. Default false. |
controls | boolean | { zoom, fullPage, rotation, reset, annotations, information } | Which controls to show. Default all. |
controlButtons | ControlButtons | Replace any control, as the Viewer's controlButtons. |
options | CloverCanvasOptions | See below. |
onReady | (handle: CanvasHandle) => void | Called once the first images are laid out and fitted. |
onViewportChange | (bounds: Rect) => void | The visible region, in image coordinates, at most once per frame. |
onMediaElement | (element: HTMLVideoElement | null) => void | A time-based Canvas's media element, for a host's own transcript. |
onEnded | () => void | Playback reached the end: a host's cue to advance. |
options
| Option | Default | Description |
|---|---|---|
backend | "auto" | "auto", "webgl2" or "canvas2d". |
gestures | clickToZoom, dblClickToZoom, pinchToZoom, scrollToZoom (false). | |
maxZoomPixelRatio | 1.1 | Largest zoom, in CSS pixels per image pixel. |
minZoomImageRatio | 0.9 | Smallest zoom, as a fraction of the home zoom. |
animationTime | 0.09 | Easing time constant in seconds. 0 under reduced motion. |
hideDelay | 2000 | Milliseconds the controls and navigator stay after the pointer stops, as the Player's. |
withCredentials | false | Send cookies with info.json and image requests. |
requestHeaders | Extra 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".