Konva – HTML5 Canvas viewport optimisation

When trying to squeeze every last morsel of optimisation out of a canvas app, we need to be concerned about NOT drawing anything we don’t need to. This means anything that the user cannot possibly see should not be drawn. I’ll be showing how to know a node is offscreen using the Konva HTML5 canvas library.

So – how to know when a node is offscreen ? The answer is quick and straightforward – we need to compare two rectangles for overlap. One is the what I’ll call the ‘viewport’, and the other is the rect bounding the node we are checking.

Looking at the image above, if da Vinci’s Mona Lisa is the stage and the red box is the container, that makes the red box our viewport and we don’t need to display anything outside of that.

The viewport is the current in-view area of the stage. Thinking about scrolling stages and other potentials to mess with the stage position, we can’t always rely on stage.pos X & Y to be at topleft of the canvas container. But we can think about this another way – the only parts of the stage we can see are bounded by the stage container. For Konva this is a plain DIV that we pass to Konva during creation and which Konva uses to house the canvas element. So, we can formulate a rectangle as

viewPort = {x: 0, y: 0, width: stageContainer.width(), height: stageContainer.height()};
if (! hasOverlap(viewPort, nodeRect){
   offscreen = true;
}

Now turning to the node rect – if we use plain stage co-ordinates we will have to reverse any movements in the stage, layer and group (x, y) positions, which will be complex and non-robust. However, Konva provides the node.getClientRect() method which does all that for us and returns the bounding rectangle of the node in units that make sense when compared to the container div. If the container div has a CSS width of 600px and the node.getClientRect() says the node’s X position is 300 then the node is half way across the container. Following that rule then, if the node’s X position is > 600 then it is off the container and cannot be seen!

This makes our code into:

viewPort = {x: 0, y: 0, width: stageContainer.width(), height: stageContainer.height()};
if (! hasOverlap(viewPort, node.getClientRect()){
   offscreen = true;
}

The hasOverlap() function here looks like below – this is a standard way to test for collision of any two non-rotated rectangles.

// Use center-center distance check for non-rotated rects.
function hasOverlap(r1, r2){

  let w1 = r1.width, h1 = r1.height;
  let w2 = r2.width, h2 = r2.height;

  let diff = {x: Math.abs((r1.x + w1/2) - (r2.x + w2/2)), 
              y: Math.abs((r1.y + h1/2) - (r2.y + h2/2))};

  let compWidth = (r1.width + r2.width)/2,
      compHeight = (r1.height + r2.height)/2;

  let hasOverlap = ((diff.x <= compWidth) && (diff.y <= compHeight)) 

  return hasOverlap;
}

Here’s the code for a demo including the above.

What do I do with this ?

Well, every time you redraw a layer there is a cost, and the less you draw the more you save. So use this approach to know if a node is offscreen, and set the node.visible() attribute as dictated.

Ok – but I always make the stage match the size of the container?

A good approach, but if you allow stage or layer dragging / scrolling or scaling then you could benefit from this technique.

Summary

Not a lot to add really – knowing when a node is completely out-of-sight will save some GPU cycles, helping improve performance. Maybe a bit, but maybe a lot.

Thanks for reading.

3 thoughts on “Konva – HTML5 Canvas viewport optimisation

  1. Great blog articles on Konva – have you considered adding your OFF-CANVAS hiding feature directly into the Konva source ?

    Like

    1. Thanks. I’m a user of Konva rather than a developer of it. If I come up with something that might be useful I create an issue on the Konva Github site and use that to prompt discussion with Anton, the lead developer. He works hard to keep the library on-point and avoid bloat from edge cases, which I agree with. I would put the off screen canvas technique outside of what the lib should include, but if you have a different perspective please go ahead and raise it with Anton. Thanks for reading the blog.

      Like

      1. Thanks for the details and again for sharing your techniques as a very proliferate user.
        Hiding elements off screen seems like a very general and useful performance opt (could still have a toggle to switch off/on), may I ask your reason for not proposing it as a core addition ?

        Like

Leave a comment