Reputation: 1090
I see for zooming and panning in Fabric JS there are the commands Canvas.setZoom, absolutePan and relativePan but can anyone show with example code how to use them, especially in using both mouse and touch events to control them?
Thanks
Upvotes: 3
Views: 2275
Reputation: 106
I don't think there is an "official" way. As I see it you have two ways you can do this, either by re-drawing the canvas content or by moving and scaling the canvas as a whole using CSS transform. The latter is vastly more performant.
I made a demo comparing drag and zoom using Fabric.js versus using CSS transform: https://codepen.io/Fjonan/pen/azoWXWJ
Since you requested it I will go into detail on how to do it using Fabric.js native functions although I recommend looking into CSS transform. I wrote a whole article about performant drag and zoom for Fabric.js.
Setup something like this:
<canvas id=canvas>
</canvas>
This code handles dragging:
const canvas = new fabric.Canvas("canvas",{
allowTouchScrolling: false,
defaultCursor: 'grab',
selection: false,
// …
})
let lastPosX,
lastPosY
canvas.on("mouse:down", dragCanvasStart)
canvas.on("mouse:move", dragCanvas)
/**
* Save reference point from which the interaction started
*/
function dragCanvasStart(event) {
const evt = event.e || event // fabricJS event or regular event
// save thew position you started dragging from
lastPosX = evt.clientX
lastPosY = evt.clientY
}
/**
* Start Dragging the Canvas using Fabric JS Events
*/
function dragCanvas(event) {
const evt = event.e || event // fabricJS event or regular event
// left mouse button is pressed if not a touch event
if (1 !== evt.buttons && !(evt instanceof Touch)) {
return
}
redrawCanvas(evt)
}
/**
* Update canvas by updating viewport transform triggering a re-render
* this is very expensive and slow when done to a lot of elements
*/
function redrawCanvas(event) {
const vpt = canvas.viewportTransform
let offsetX = vpt[4] + event.clientX - (lastPosX || 0)
let offsetY = vpt[5] + event.clientY - (lastPosY || 0)
vpt[4] = offsetX
vpt[5] = offsetY
lastPosX = event.clientX
lastPosY = event.clientY
canvas.setViewportTransform(vpt)
}
And this code will handle zoom:
canvas.on('mouse:wheel', zoomCanvasMouseWheel)
/**
* Zoom canvas when user used mouse wheel
*/
function zoomCanvasMouseWheel(event) {
const delta = event.e.deltaY
let zoom = canvas.getZoom()
zoom *= 0.999 ** delta
const point = {x: event.e.offsetX, y: event.e.offsetY}
zoomCanvas(zoom, point)
}
/**
* Zoom the canvas content using fabric JS
*/
function zoomCanvas(zoom, aroundPoint) {
canvas.zoomToPoint(aroundPoint, zoom)
canvas.renderAll()
}
Now for touch events we have to attach our own event listeners since Fabric.js does not (yet) support touch events as part of their handled listeners.
Fabric.js will create its own wrapper element canvas-container
which I access here using canvas.wrapperEl
.
let pinchCenter,
initialDistance
canvas.wrapperEl.addEventListener('touchstart', (event) => {
dragCanvasStart(event.targetTouches[0])
pinchCanvasStart(event)
})
canvas.wrapperEl.addEventListener('touchmove', (event) => {
dragCanvas(event.targetTouches[0])
pinchCanvas(event)
})
/**
* Save the distance between the touch points when starting the pinch
*/
function pinchCanvasStart(event) {
if (event.touches.length !== 2) {
return
}
initialDistance = getPinchDistance(event.touches[0], event.touches[1])
}
/**
* Start pinch-zooming the canvas
*/
function pinchCanvas(event) {
if (event.touches.length !== 2) {
return
}
setPinchCenter(event.touches[0], event.touches[1])
const currentDistance = getPinchDistance(event.touches[0], event.touches[1])
let scale = (currentDistance / initialDistance).toFixed(2)
scale = 1 + (scale - 1) / 20 // slows down scale from pinch
zoomCanvas(scale * canvas.getZoom(), pinchCenter)
}
/**
* Putting touch point coordinates into an object
*/
function getPinchCoordinates(touch1, touch2) {
return {
x1: touch1.clientX,
y1: touch1.clientY,
x2: touch2.clientX,
y2: touch2.clientY,
}
}
/**
* Returns the distance between two touch points
*/
function getPinchDistance(touch1, touch2) {
const coord = getPinchCoordinates(touch1, touch2)
return Math.sqrt(Math.pow(coord.x2 - coord.x1, 2) + Math.pow(coord.y2 - coord.y1, 2))
}
/**
* Pinch center around wich the canvas will be scaled/zoomed
* takes into account the translation of the container element
*/
function setPinchCenter(touch1, touch2) {
const coord = getPinchCoordinates(touch1, touch2)
const currentX = (coord.x1 + coord.x2) / 2
const currentY = (coord.y1 + coord.y2) / 2
pinchCenter = {
x: currentX,
y: currentY,
}
}
Again, this is a very expensive way to handle zoom and drag since it forces Fabric.js to re-render the content on every frame. Even when limiting the event calls using throttle you will not get smooth performance especially on mobile devices.
Upvotes: 0