export default function (scrawl, el) {Snippets included in the Scrawl-canvas demo/snippets folder
Related files:
Purpose: Displays a pan effect on an image, where users can click and drag the image to explore it
Function input:
Function output: a Javascript object will be returned, containing the following attributes
{
element // the Scrawl-canvas wrapper for the DOM element supplied to the function
canvas // the Scrawl-canvas wrapper for the snippet's canvas
animation // the Scrawl-canvas animation object
demolish // remove the snippet from the Scrawl-canvas library
}
import panImage from './relative/or/absolute/path/to/this/file.js';
let myElements = document.querySelectorAll('.some-class');
myElements.forEach(el => panImage(scrawl, el));
Effects on the element:
transparentexport default function (scrawl, el) {Apply the snippet to the DOM element
const snippet = scrawl.makeSnippet({
domElement: el,
});
if (snippet) {Set some convenience variables
const canvas = snippet.canvas,
animation = snippet.animation,
group = canvas.base.name,
name = snippet.element.name;Set the DOM element’s background color to transparent so we can see the new canvas
el.style.backgroundColor = 'transparent';Grab all the child <img> elements
const images = el.querySelectorAll('img');If the coder gave the DOM element a sane id attribute value then all our names will be sensible names. If not, then Scrawl-canvas will have computer-generated an id for the element. These generated names often include characters that do not sit well with the CSS/JS querySelector string requirements. So we have to make the name safe to be used as a search string
let imgName = `${name}-image`;
imgName = imgName.replace('.', '');Update all the <img> elements we found. In particular:
images.forEach((img, index) => {
let classes = img.getAttribute('class')
if (!classes) classes = '';
classes += ` ${imgName}`;
img.setAttribute('class', classes);
img.id = `${imgName}-${index}`;
img.style.display = 'none';
});Import all the images we found into Scrawl-canvas
scrawl.importDomImage(`.${imgName}`);Configuration - coders can set ‘data-x’ and ‘data-y’ attributes on the DOM element to indicate which area of the image should be displayed in the new canvas when the page first loads
const copyStartX = el.dataset.x || 0,
copyStartY = el.dataset.y || 0;We will display the first image we found in a Picture entity
const [w, h] = canvas.get('dimensions');
const asset = scrawl.library.asset[`${imgName}-0`];
const panImage = scrawl.makePicture({
name: `${imgName}-panimage`,
group,
asset,
width: w,
height: h,
copyWidth: w,
copyHeight: h,
copyStartX,
copyStartY,
});
let cursor = 'grab',
lastX = 0,
lastY = 0,
aspectWidth = 1,
aspectHeight = 1;The pan effect, where the user can click and drag the image within its container to explore large images in small areas. To achieve the effect we split the functionality across three separate event listeners, which capture the three key actions associated with the effect: starting a drag; continuing a drag; and ending a drag.
scrawl.addListener('down', () => {
const here = canvas.here;
if (here.active && 'grab' === cursor) {
cursor = 'grabbing';
el.style.cursor = cursor;
const {x, y} = here;
lastX = x;
lastY = y;
}
}, el);
scrawl.addListener('move', () => {
if ('grabbing' === cursor) {
const {x, y} = canvas.here;
const dx = lastX - x,
dy = lastY - y;
panImage.setDelta({
copyStartX: dx * aspectWidth,
copyStartY: dy * aspectHeight,
});
lastX = x;
lastY = y;
}
}, el);
scrawl.addListener(['up', 'leave'], () => {
if ('grabbing' === cursor) {
cursor = 'grab';
el.style.cursor = cursor;
const {x, y} = canvas.here;
const dx = lastX - x,
dy = lastY - y;
panImage.setDelta({
copyStartX: dx * aspectWidth,
copyStartY: dy * aspectHeight,
});
lastX = 0;
lastY = 0;
}
}, el);We’re checking dimensions here partly because images load asynchronously and during the early part of that process their <img> elements will supply incorrect information to the script which is trying to set up the canvas element which will (eventually) host their image data. But we also need to check because the script has no way of knowing what sort of image it is dealing with - for instance an <img> element with a “srcset” attribute will load new images if the user decides to expand their browser’s window - which would have an impact on the assumptions we make in our scaling and dragging calculations.
const checkDimensions = function () {
const [canvasWidth, canvasHeight] = canvas.get('dimensions');
const [imageWidth, imageHeight] = panImage.get('dimensions');check to see if there’s been any resize browser resize or CSS layout change that affects our DOM element
if (canvasWidth !== imageWidth || canvasHeight !== imageHeight) {
const [sourceWidth, sourceHeight] = panImage.get('sourceDimensions');We want the image we paint in our canvas to keep its asset’s original aspect ratio
aspectWidth = canvasWidth / sourceWidth;
aspectHeight = canvasHeight / sourceHeight;We want the zoom level to be x3 (along the width) with respect to the DOM element’s dimensions
const setToHalfWidth = 50 / (aspectWidth * 100);
const ratioWidth = aspectWidth * setToHalfWidth * 100,
ratioHeight = aspectHeight * setToHalfWidth * 100;
panImage.set({
width: canvasWidth,
height: canvasHeight,
copyWidth: ratioWidth + '%',
copyHeight: ratioHeight + '%',
});
}
};Invoke our ‘checkDimensions’ function at the start of every Display cycle
animation.set({
commence: checkDimensions,
});
}Return the snippet, so coders can access the snippet’s parts - in case they need to tweak the output to meet the web page’s specific requirements
return snippet;
}