Hammer.js for Pinch to Zoom Paper.js

1,016 views
Skip to first unread message

ksz...@jerviswebb.com

unread,
Aug 10, 2017, 11:22:07 AM8/10/17
to Paper.js
Just wondering if anyone here has played with Hammer.js and Paper.js in the same setting.
I'm building an Angular app where I'm using Paper.js to display information on a canvas.
I'm using Hammer.js for gestures on a mobile device since Angular has some built in directives for Hammer
All I have to do is create a binding for the event on the element I want Hammer to be on, and then just create a handler for the event.

So I this is how I have everything setup in my view. The canvas has the height & width that it does to just initialize it, as I load Paper when this component starts, I update the height & width values
<div #graphicsContainer id="graphicsContainer" class="col-xs-12 componentContents" style="text-align:center; overflow:hidden;" (panstart)="onPanStart($event)" (panmove)="onPanMove($event)" (pinchstart)="onPinchStart($event)" (pinchmove)="onPinch($event)" [style.background-color]="bgColor">
    <div id="grab" [style.marginLeft.px]="x" [style.marginTop.px]="y" [style.transform]="'scale(' + scale + ',' + scale + ')'">
        <canvas id="theCanvas" width="1000" height="500"></canvas>
    </div>
    <graphics-help></graphics-help>
</div>

In my Angular component this is what I have defined for my functions with Hammer
    /**     * Angular has directives built in for Hammer.js     */    //Variables for Hammer.js    x: number = 0;    y: number = 0;    scale: number = 1;    startX: number = 0;    startY: number = 0;    startScale: number = 1;    /**     * onPanStart     * Event Listener for panstart - hammer.js     * @param event     */    onPanStart(event: any): void {        event.preventDefault();        this.startX = this.x;        this.startY = this.y;    }    /**     * onPanMove     * Event Listener for panmove - hammer.js     * @param event     */    onPanMove(event: any): void {        event.preventDefault();        this.x = this.startX + event.deltaX;        this.y = this.startY + event.deltaY;    }    /**     * onPinchStart     * Event Listener for pinchstart - hammer.js     * @param event     */    onPinchStart(event: any): void {        event.preventDefault();        this.startScale = this.scale;    }    /**     * onPinch     * Event Listener for pinchmove - hammer.js     * @param event     */    onPinch(event: any): void {        event.preventDefault();        this.scale = this.startScale * event.scale;    }

My main items of concern here are my onPinchStart and onPinch functions.
With the way I have this currently written, I'm scaling the div that contains the canvas element I'm using for Paper. My intention with this was to have everything still in view without having to resize the canvas from Paper. I figured all the click listeners on my Paper Items would scale as the canvas scaled, however I ended up breaking all the click listeners on my Paper items when the scale is anything except 1.
The click listeners still exist because I found on a tablet I was using that the scale was 0.9 (roughly, thanks Chrome DevTools), and the click listener was still kind of on the Paper Item I was looking at, but it was only half on it.
Doing some searching around the web, I found out essentially Paper was unaware that the canvas had been scaled, so instead of scaling the div containing the canvas, from within my Hammer functions, that's where I should handle scaling my Paper canvas.
I'm having a bit of trouble getting this to work out however.
I've defined some more variables to keep track of the canvas' height & width, and I initialize newWidth & newHeight when the background is added to the Paper canvas. My thought now is to use Paper's scale function on the canvas, in addition to update the viewSize of the canvas so my pinch to zoom gesture performs how it normally would. It doesn't appear to be working all too well though, can anyone help me out with this? If you would like a runnable demo, I can provide one if needed. I just haven't taken the time to put it on GitHub 

    startWidth: number;
    startHeight: number;
    newWidth: number;
    newHeight: number;

     /**
     * onPinchStart
     * Event Listener for pinchstart - hammer.js
     * @param event
     */
    onPinchStart(event: any): void {
        event.preventDefault();
        this.startScale = this.scale;
        this.startHeight = this.newHeight;
        this.startWidth = this.newWidth;
    }

    /**
     * onPinch
     * Event Listener for pinchmove - hammer.js
     * @param event
     */
    onPinch(event: any): void {
        event.preventDefault();
        this.scale = this.startScale * event.scale;
        paper.view.scale(this.scale, [0,0]);
        console.log(this.scale);
        this.newHeight = this.startHeight * event.scale;
        this.newWidth = this.startWidth * event.scale;
        console.log(this.newWidth + " x " + this.newHeight);
        paper.view.viewSize.width = this.newWidth;
        paper.view.viewSize.height = this.newHeight;
    }




Rick Mohr

unread,
Oct 2, 2021, 2:52:49 PM10/2/21
to Paper.js

Here's a relatively compact solution (codepen):

function initPinchZoom() {
    const canvasElement = paper.view.element;
    const box = canvasElement.getBoundingClientRect();
    const offset = new paper.Point(box.left, box.top);

    const hammer = new Hammer(canvasElement, {});
    hammer.get('pinch').set({ enable: true });

    let pinching, startMatrix, startMatrixInverted, p0ProjectCoords;

    hammer.on('pinchstart', e => {
        startMatrix = paper.view.matrix.clone();
        startMatrixInverted = startMatrix.inverted();
        const p0 = getCenterPoint(e);
        p0ProjectCoords = paper.view.viewToProject(p0);
    });

    hammer.on('pinch', e => {
        // Translate and scale view using pinch event's 'center' and 'scale' properties.
        // Translation computes center's distance from initial center (considering current scale).
        const p = getCenterPoint(e);
        const pProject0 = p.transform(startMatrixInverted);
        const delta = pProject0.subtract(p0ProjectCoords).divide(e.scale);
        paper.view.matrix = startMatrix.clone().scale(e.scale, p0ProjectCoords).translate(delta);
    });

    function getCenterPoint(e) {
        return new paper.Point(e.center.x, e.center.y).subtract(offset);
Reply all
Reply to author
Forward
0 new messages