• Jump To … +
    ./demo/canvas-001.js ./demo/canvas-002.js ./demo/canvas-003.js ./demo/canvas-004.js ./demo/canvas-005.js ./demo/canvas-006.js ./demo/canvas-007.js ./demo/canvas-008.js ./demo/canvas-009.js ./demo/canvas-010.js ./demo/canvas-011.js ./demo/canvas-012.js ./demo/canvas-013.js ./demo/canvas-014.js ./demo/canvas-015.js ./demo/canvas-016.js ./demo/canvas-017.js ./demo/canvas-018.js ./demo/canvas-019.js ./demo/canvas-020.js ./demo/canvas-021.js ./demo/canvas-022.js ./demo/canvas-023.js ./demo/canvas-024.js ./demo/canvas-025.js ./demo/canvas-026.js ./demo/canvas-027.js ./demo/canvas-028.js ./demo/canvas-029.js ./demo/canvas-030.js ./demo/canvas-031.js ./demo/canvas-032.js ./demo/canvas-033.js ./demo/canvas-034.js ./demo/canvas-035.js ./demo/canvas-036.js ./demo/canvas-037.js ./demo/canvas-038.js ./demo/canvas-039.js ./demo/canvas-040.js ./demo/canvas-041.js ./demo/canvas-042.js ./demo/canvas-043.js ./demo/canvas-044.js ./demo/canvas-045.js ./demo/canvas-046.js ./demo/canvas-047.js ./demo/component-001.js ./demo/component-002.js ./demo/component-003.js ./demo/component-004.js ./demo/component-005.js ./demo/component-006.js ./demo/component-007.js ./demo/core-001.js ./demo/dom-001.js ./demo/dom-002.js ./demo/dom-003.js ./demo/dom-004.js ./demo/dom-005.js ./demo/dom-006.js ./demo/dom-007.js ./demo/dom-008.js ./demo/dom-009.js ./demo/dom-010.js ./demo/dom-011.js ./demo/dom-012.js ./demo/dom-013.js ./demo/dom-014a.js ./demo/dom-014b.js ./demo/dom-014c.js ./demo/dom-015.js ./demo/dom-016.js ./demo/filters-001.js ./demo/filters-002.js ./demo/filters-003.js ./demo/filters-004.js ./demo/filters-005.js ./demo/filters-006.js ./demo/filters-007.js ./demo/filters-008.js ./demo/filters-009.js ./demo/filters-010.js ./demo/filters-011.js ./demo/filters-012.js ./demo/filters-013.js ./demo/filters-014.js ./demo/filters-015.js ./demo/filters-016.js ./demo/filters-017.js ./demo/filters-018.js ./demo/filters-019.js ./demo/filters-020.js ./demo/filters-501.js ./demo/filters-502.js ./demo/filters-503.js ./demo/filters-504.js ./demo/filters-505.js ./demo/particles-001.js ./demo/particles-002.js ./demo/particles-003.js ./demo/particles-004.js ./demo/particles-005.js ./demo/particles-006.js ./demo/particles-007.js ./demo/particles-008.js ./demo/particles-009.js ./demo/particles-010.js ./demo/particles-011.js ./demo/particles-012.js ./demo/particles-013.js ./demo/particles-014.js ./demo/particles-015.js ./demo/particles-016.js ./demo/temp-001.js ./demo/temp-inkscapeSvgFilters.js
  • ¶

    Demo Canvas 037

    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.

    Run code

    import scrawl from '../source/scrawl.js';
  • ¶

    Scene state

    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 values
    • scale variables to track scale limits and value
    • from variables to track the copyStart coordinate
    let naturalWidth = 4160,
        naturalHeight = 3120,
        frameDimension = 500,
        maxScale = naturalHeight / frameDimension,
        currentScale = maxScale,
        minScale = 1,
        fromLeft = 0,
        fromTop = 0;
  • ¶

    Convenience variables to access key Scrawl-canvas objects stored in the library

    let canvas = scrawl.library.canvas.mycanvas, 
        base = canvas.base,
        baseGroup = scrawl.library.group[base.name];
  • ¶

    Scene setup

    canvas.set({
    
        backgroundColor: 'honeydew',
    
        width: frameDimension,
        height: frameDimension,
    
        checkForResize: true,
        isComponent: true,
    
    }).setAsCurrentCanvas();
  • ¶

    The Picture entity will cover the entire displayed canvas

    let piccie = scrawl.makePicture({
    
        name: 'river-pic',
    
        imageSource: './img/river.jpg',
    
        width: '100%',
        height: '100%',
    
        copyWidth: currentScale * frameDimension,
        copyHeight: currentScale * frameDimension,
    
        copyStartX: fromLeft,
        copyStartY: fromTop,
    });
  • ¶

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    let report = function () {
    
        let testTicker = Date.now(),
            testTime, testNow,
            testMessage = document.querySelector('#reportmessage');
    
        return function () {
    
            testNow = Date.now();
            testTime = testNow - testTicker;
            testTicker = testNow;
    
            testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`;
        };
    }();
  • ¶

    Create the Display cycle animation

    const demoAnimation = scrawl.makeRender({
    
        name: "demo-animation",
        target: canvas,
        afterShow: report,
    });
  • ¶

    Bespoke user interaction

    We shall build the “zoom” and “pan” effects using event listeners.

    • For the purposes of accessibility these events should be extended to include keyboard interactions
    • Mobile devices should also include multitouch gestures - such activity is often best captured using a dedicated third party Javascript library, for example hammer.js

    Zoom effect

    scrawl.addNativeListener('wheel', (e) => {
    
        e.preventDefault();
    
        let oldScale = currentScale,
            here = canvas.here;
    
        currentScale += (e.deltaY * -0.0005);
    
        if (currentScale > maxScale) currentScale = maxScale;
        else if (currentScale < minScale) currentScale = minScale;
    
        let 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', (e) => {
    
        let here = base.here,
            target = baseGroup.getArtefactAt(base.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', (e) => {
    
        let here = base.here;
    
        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'], (e) => {
    
        draggingArtefact = false;
        currentDragX = 0;
        currentDragY = 0;
    
    }, canvas.domElement);
  • ¶

    Development and testing

    console.log(scrawl.library);