import * as scrawl from '../source/scrawl.js';
import { reportSpeed } from './utilities.js';
Pan and zoom using a Picture entity
Scrawl-canvas does not include built-in functionality to easily “zoom” (expand/contract the contents of) a <canvas> element. Nor does it include functionality to “pan” (move in any direction through the contents of) the canvas. The use cases for, and the user interactions for controlling, such functionality are too varied; any attempt to capture a particular approach within the library’s code base would necessarily make other, equally valid, approaches more problematic for developers to implement.
The code in this demo demonstrates how to capture user actions in events, which can then be used to build a “zoom and pan” experience. The demo uses a Picture entity, because that entity does include functionality which prevents the entity from going beyond the borders of its image (or video) asset.
import * as scrawl from '../source/scrawl.js';
import { reportSpeed } from './utilities.js';
Because we need to manually calculate the copy position and dimensions for the Picture entity, we need to create state to hold known values and record changes between user interactions
naturalWidth/Height
- the image asset’s natural dimensions (fixed value)frameDimension
- the <canvas> element’s (non-responsive) dimension valuesscale
variables to track scale limits and valuefrom
variables to track the copyStart coordinateconst naturalWidth = 4160,
naturalHeight = 3120,
frameDimension = 500,
maxScale = naturalHeight / frameDimension,
minScale = 1;
let currentScale = maxScale,
fromLeft = 0,
fromTop = 0;
const canvas = scrawl.findCanvas('mycanvas');
Namespacing boilerplate
const namespace = canvas.name;
const name = (n) => `${namespace}-${n}`;
The Picture entity will cover the entire displayed canvas
const piccie = scrawl.makePicture({
name: name('river-pic'),
imageSource: './img/river.webp',
width: '100%',
height: '100%',
copyWidth: currentScale * frameDimension,
copyHeight: currentScale * frameDimension,
copyStartX: fromLeft,
copyStartY: fromTop,
});
Function to display frames-per-second data, and other information relevant to the demo
const report = reportSpeed('#reportmessage');
Create the Display cycle animation
scrawl.makeRender({
name: name('animation'),
target: canvas,
afterShow: report,
});
We shall build the “zoom” and “pan” effects using event listeners.
Zoom effect
scrawl.addNativeListener('wheel', (e) => {
e.preventDefault();
const oldScale = currentScale,
here = canvas.here;
currentScale += (e.deltaY * -0.0005);
if (currentScale > maxScale) currentScale = maxScale;
else if (currentScale < minScale) currentScale = minScale;
const delta = (frameDimension * oldScale) - (frameDimension * currentScale);
fromLeft += delta * here.normX;
fromTop += delta * here.normY;
piccie.set({
copyWidth: currentScale * frameDimension,
copyHeight: currentScale * frameDimension,
copyStartX: fromLeft,
copyStartY: fromTop,
});
}, canvas.domElement);
Define some additional scene state, specifically for the “pan” effect
let draggingArtefact = false,
currentDragX = 0,
currentDragY = 0;
Pan effect, split across three separate event listeners (starting, during, ending)
scrawl.addListener('down', () => {
const here = canvas.getBaseHere(),
target = canvas.get('baseGroup').getArtefactAt(here);
draggingArtefact = (target && target.artefact && target.artefact.type === 'Picture') ?
target.artefact :
false;
if (draggingArtefact) {
currentDragX = here.x;
currentDragY = here.y;
}
}, canvas.domElement);
scrawl.addListener('move', () => {
const here = canvas.getBaseHere();
if (draggingArtefact) {
fromLeft += (currentDragX - here.x) * currentScale;
fromTop += (currentDragY - here.y) * currentScale;
currentDragX = here.x;
currentDragY = here.y;
if (fromLeft < 0) fromLeft = 0;
else if (fromLeft > naturalWidth - frameDimension) fromLeft = naturalWidth - frameDimension;
if (fromTop < 0) fromTop = 0;
else if (fromTop > naturalHeight - frameDimension) fromTop = naturalHeight - frameDimension;
piccie.set({
copyStartX: fromLeft,
copyStartY: fromTop,
});
}
}, canvas.domElement);
scrawl.addListener(['up', 'leave'], () => {
draggingArtefact = false;
currentDragX = 0;
currentDragY = 0;
}, canvas.domElement);
console.log(scrawl.library);