konvajs / konva

Konva.js is an HTML5 Canvas JavaScript framework that extends the 2d context by enabling canvas interactivity for desktop and mobile applications.
http://konvajs.org/
Other
11.07k stars 896 forks source link

shape.getClientRect() is not updated after layer.cache() #1759

Closed VanquishedWombat closed 4 weeks ago

VanquishedWombat commented 2 months ago

The shape.getClientRect() function should return the rect relative to the canvas element.

In cases of large number of shapes we suggest for performance reasons to cache the layer.

If we apply scale to the stage after caching the layer we expect shape.getClientRect() to return a different value than the initial one.

The issue is that after layer.cache() the shape.getClientRect() is unchanged. This is unexpected behaviour and not consistent.

Under the covers Konva uses caching of absolute transforms. When a container such as layer is cached, for performance reasons the descendant nodes do NOT have their absolute transform cache value reset.

Proposed solution: Could it be possible to mark the cached absolute transform values of the descendent nodes as dirty - there is already code for this. The idea would be that when we next call for a shape.getClientRect() it's abs transform would be recalculated because it was flagged as dirty. This would cause the same process up the ascendent chain, and so we would get a correct client rect. This would add some overhead but as it is solely resetting the dirty flag or clearing the cached values, it is data-only rather than any redrawing or processor heavy work.

Version: Konva 8 & 9.

Demo: https://codepen.io/JEE42/pen/RwmwpMx?

VanquishedWombat commented 2 months ago

I wrote a small test demo for my proposed solution and it appears to work.

function clearCachedTransform(node){
  node._clearCache('absoluteTransform')   // <<<< this is an existing Konva method

  if (['Layer','Group'].includes(node.getClassName())){
    for (const child of node.getChildren()){
      doClearCachedTransform(child)
    }
  }  
}

What we need to do is call this when a container (stage / layer / group) is cached It clears the cached version of the absolute transform for the container and all descendants. Next time we ask for a getClientRect() the absolute transform is re-assembled and re-cached for the entire parent chain up to the node initially passed in to the function.

I have a hunch that this is only needed when the caching us after a new transform is applied. I came across the issue in a case where I cached a layer then scaled it and I was looking at a child shapes client rect, noticing it should have but did not change after the layer was scaled. I therefore expect than any transformation of the layer which would have the effect of moving the child shapes from the perspective of the canvas element would require this cached absolute transform reset approach.

Just for interest, in my codepen demo with 19952 shapes the caches are cleared in less than 5ms - this is with all the codepen bloat loaded so should be faster in leaner cases.

VanquishedWombat commented 2 months ago

Update - as proposed in my last comment, the issue of caching the absolute transform appears to also be present when I drag the stage. Remember it is the layer that is cached and the layer is translated (in the perspective of the canvas) with the stage drag.