Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to improve canvas fabric.js performance with large number of Objects

I need make an App that has about 30k Objects, a user can Pan, Zoom or "Select on click" any of those objects. Fabric.js Canvas is being used

I have done the same using SVG's and the svg-pan-zoom plugin (no Canvas Element) with better results

Problem: there is a significant Lag while Zooming, Panning or Object on Click

  1. will removing Fabric.js improve performance?
  2. will switching to WebGL improve performance?

Have tried Fabric specific options

fabric.Object.prototype.objectCaching = false;
fabric.Object.prototype.statefullCache = false;
fabric.Object.prototype.noScaleCache = true;
fabric.Object.prototype.needsItsOwnCache = false;

UPDATE Heres the updated Fiddle

for reference :

  1. canvas-vs-svg-vs-div Stackoverflow

  2. Stackoverflow

like image 575
Ritin Avatar asked Mar 31 '17 11:03

Ritin


1 Answers

Don't Render in IO EVENTS!

Though not a complete fix to the update speed this answer will about double the interaction speed.

A common, almost standard, mistake made with mouse and event interaction with the canvas (and DOM) is to delegate rendering to mouse/touch events. This is very bad practice as mouse events fire at much higher rates than the display can display. It becomes worse when your rendering time is high as you queue up mouse events (pseudo render events) and do a re render for every movement of the mouse

Note blocking code will stop mouse events but as soon as the engine is idle the mouse will start firing at full rate again.

Use the mouse events just to get the mouse state. Use an animation loop that is synced to the display to render only when needed and there is time available. Things like the wheel and mouse movement deltas should be recorded cumulatively.

mouse.dx += event.movementX; 
mouse.dy += event.movementY; 
mouse.wheel += event.wheelDelta;

And consume them in the main render loop...

function update(){

    // ... code to use mouse
    // consume deltas
    mouse.x = mouse.y = mouse.wheel = 0;

...this ensures that the mouse state is accurately followed when you may have many mouse events between render updates.

Example, separating events from rendering.

Change you code in the fiddle you provided to the following, on my machine it about doubled the rendering speed (which is still very slow).

// from just after the function applyZoom replace all the code 
var mouse = {  // holds the mouse state
    x : 0,
    y : 0,
    down : false,
    w : 0,
    delta : new fabric.Point(0,0),
}
// event just track mouse state
function zoom(e) {
    if(e != null) { e.preventDefault() }
    var evt=window.event || e;
    mouse.x = e.offsetX;
    mouse.y = e.offsetY;
    mouse.w += evt.detail? evt.detail*(-120) : evt.wheelDelta;
    return false;
}
canvas.on('mouse:up', function (e) { mouse.down = false });
canvas.on('mouse:out', function (e) { mouse.down = false });
canvas.on('mouse:down', function (e) { mouse.down = true });
canvas.on('mouse:move', function(e) {
    if (e && e.e) {
        mouse.delta.x += e.e.movementX;
        mouse.delta.y += e.e.movementY;
    }
});
// main animation loop
function update(){
    if(mouse.w !== 0){  // if the wheel has moved do zoom
        var curZoom = canvas.getZoom();
         canvas.zoomToPoint(
             { x : mouse.x, y: mouse.y }, 
             canvas.getZoom() + mouse.w / 4000
         );
         mouse.w = 0; // consume wheel delta
    }else if(mouse.down) {  // if mouse button down
         canvas.relativePan(mouse.delta);
    }
    // consume mouse delta
    mouse.delta.x = 0;
    mouse.delta.y = 0;
    requestAnimationFrame(update);
}
requestAnimationFrame(update);
like image 193
Blindman67 Avatar answered Oct 15 '22 01:10

Blindman67