Skip to content

Instantly share code, notes, and snippets.

@iyobo
Last active July 12, 2021 09:53
Show Gist options
  • Save iyobo/4d2ff6a370011675ebf1d561e830be1e to your computer and use it in GitHub Desktop.
Save iyobo/4d2ff6a370011675ebf1d561e830be1e to your computer and use it in GitHub Desktop.
A PixiViewport plugin: Better Scroll/Zoom Behavior on trackpads with two-finger scoll and zoom.
import {Plugin, Viewport} from 'pixi-viewport';
import {DisplayObject} from 'pixi.js';
type Options = {
moveSpeed: number;
moveReverse: boolean;
zoomSpeed: number;
zoomReverse: boolean;
limitToObjectBounds?: DisplayObject;
limitToObjectBoundsBy?: number;
};
const defaults: Options = {
moveSpeed: 1,
moveReverse: false,
zoomSpeed: 1,
zoomReverse: false,
limitToObjectBoundsBy: 500
};
/**
Better Scroll/Zoom Behavior on trackpads or devices that with two-finger scoll and zoom.
Usage example:
const viewport = new Viewport({
screenWidth: window.innerWidth,
screenHeight: window.innerHeight,
worldWidth: 1000,
worldHeight: 1000,
interaction: app.renderer.plugins.interaction,
});
...
viewport.plugins.add('wheel', new PinchToZoomAndMove(viewport, {
zoomReverse: true
}));
**/
export class PinchToZoomAndMove extends Plugin {
private parent: Viewport;
private options: Options;
private limitToObjectBounds: DisplayObject;
private limitToObjectBoundsBy: number;
private moveReverse: boolean;
private zoomReverse: boolean;
constructor(parent: Viewport, options: Partial<Options>) {
super(parent);
this.parent = parent;
this.options = {...defaults, ...options};
this.moveReverse = this.options.moveReverse ? 1 : -1;
this.zoomReverse = this.options.zoomReverse ? 1 : -1;
this.limitToObjectBounds = this.options.limitToObjectBounds;
this.limitToObjectBoundsBy = this.options.limitToObjectBoundsBy;
}
wheel(event: WheelEvent) {
if (event.ctrlKey) {
this.zoom(event);
} else {
this.pan(event);
}
}
private pan(event: WheelEvent) {
// console.log('pan')
this.parent.x +=
event.deltaX * this.options.moveSpeed * this.moveReverse;
this.parent.y +=
event.deltaY * this.options.moveSpeed * this.moveReverse;
// FIXME: Attempt to limit panning to specific bounds. WIP. Can't figure this out.
if (this.limitToObjectBounds) {
// const b = this.parent.getBounds();
// const p = this.limitToObjectBoundsBy;
//
//
// const leftLimit = b.x - p;
// // @ts-ignore
// const rightLimit = b.x - b.width + p;
// const topLimit = b.y - p;
// // @ts-ignore
// const bottomLimit = b.y + b.height + p;
// if (this.parent.x < leftLimit) this.parent.x = leftLimit;
// if (this.parent.x > rightLimit) this.parent.x = rightLimit;
// if (this.parent.y < topLimit) this.parent.y = topLimit;
// if (this.parent.y > bottomLimit) this.parent.y = bottomLimit;
// console.log('pan limit', {
// x: this.parent.x,
// y: this.parent.y,
// xfac: this.parent.x - b.x,
// xScale: this.parent.scale.x,
// xKol: b.x / this.parent.scale.x,
// dx: event.deltaX,
// dy: event.deltaY,
// b, leftLimit, rightLimit, topLimit, bottomLimit, p
// },);
}
}
minZoom = 0.25;
maxZoom = 0.95;
private zoom(event: WheelEvent) {
const delta = 1 - ((this.zoomReverse * event.deltaY * this.options.zoomSpeed) / 250);
const point = (this.parent as any).input.getPointerPosition(event);
const oldPoint = this.parent.toLocal(point);
this.parent.scale.x *= delta;
this.parent.scale.y *= delta;
// FIXME: zoom clamping (WIP)
let doProtoMove = true;
if (this.parent.scale.x < this.minZoom) {
this.parent.scale.x = this.minZoom;
doProtoMove = false;
} else if (this.parent.scale.x > this.maxZoom) {
this.parent.scale.x = this.maxZoom;
doProtoMove = false;
}
if (this.parent.scale.y < this.minZoom) {
this.parent.scale.y = this.minZoom;
doProtoMove = false;
} else if (this.parent.scale.y > this.maxZoom) {
this.parent.scale.y = this.maxZoom;
doProtoMove = false;
}
const newPoint = this.parent.toGlobal(oldPoint);
this.parent.x += point.x - newPoint.x;
this.parent.y += point.y - newPoint.y;
}
}
@ilija-trbogazov
Copy link

You should emit the wheel event inside the wheel method:

wheel(e) {
        if (e.ctrlKey) {
            this.zoom(e);
        } else {
            this.pan(e);
        }

        this.parent.emit('wheel', { wheel: { dx: e.deltaX, dy: e.deltaY, dz: e.deltaZ }, event: e, viewport: this.parent });
    }

And you can take advantage of the zoomClamp build in mechanism.

zoom(event) {
        const delta = 1 - ((this.zoomReverse * event.deltaY * this.options.zoomSpeed) / 250);

        const point = this.parent.input.getPointerPosition(event);
        const oldPoint = this.parent.toLocal(point);

        this.parent.scale.x *= delta;
        this.parent.scale.y *= delta;

        const clampZoom = this.parent.plugins.get('clamp-zoom');
        if (clampZoom) {
            clampZoom.clamp();
        }

        const newPoint = this.parent.toGlobal(oldPoint);
        this.parent.x += point.x - newPoint.x;
        this.parent.y += point.y - newPoint.y;
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment