konvajs / react-konva

React + Canvas = Love. JavaScript library for drawing complex canvas graphics using React.
https://konvajs.github.io/docs/react/
MIT License
5.8k stars 260 forks source link

Free drawing performance #791

Closed mfranzen0906 closed 10 months ago

mfranzen0906 commented 10 months ago

I am currently building a whiteboard app with konva in react.

I looked at the free drawing example and took it as the base code. But when I create like 80 lines, the performance on my iPad starts to drop rapidly. I made a few optimizations like not always updating the state when a new point is added to the currently drawn line. Instead I have another state just for the currently drawn line which gets updated. After the touch ends, the currently drawn line gets added to overall state with the canvas data. The currently drawn line is in a separate layer.

I also deactivated listening for the layers. The state is managed by "zustand" and currently I am using a lot to "memo". According to the console output, the lines don't get re-rendered, while I draw a new line. After the new line is added to the other lines, all of them get unfortunately rendered. I couldn't figure out a way to prevent this yet.

Question:

Do you have recommendations, how I could increase the performance? The app shall be used for exams, hence the free drawing part is very important and can become rather big.

lavrton commented 10 months ago
  1. You have to measure the performance. What exactly is slow? React reconciler? Konva rendering?
  2. Konva is not ideal for heavy free handle renders, by default Konva re-renders full scene. Usually, it is not very necessary for drawing apps
  3. Use full power cache. For example, when you finish drawing, you can cache all previous lines, so Konva don't have re-render them again. Or you can convert all lines into bitmap image with external canvas manually.
mfranzen0906 commented 10 months ago

So instead of working with memo and so on, would you rather recommend going with the cache? Do you have a short example, how to work with cache in react?

lavrton commented 10 months ago

So instead of working with memo and so on, would you rather recommend going with the cache?

First, you need to measure what is slow. it doesn't make sense to optimize Konva rendering if React part is slow.

Do you have a short example, how to work with cache in react?

Access node via https://konvajs.org/docs/react/Access_Konva_Nodes.html Then use Konva mathods for cache.

mfranzen0906 commented 10 months ago

Thanks so far. Interestingly, caching the background image (Din A4 paper) made the performance even worse and the image wasn't shown on my iPad anymore.

I guess I will try to measure the performance in a next step.. Currently it feels like whenever I tried to optimize something, I got worse.

Do you think, that a whiteboard app with free drawing on multiple Din A4 size pages is possible with Konva and React in general?

lavrton commented 10 months ago

Without knowing the details, I can't say whether it is really possible or not. Sounds like yes, but may depend on some use cases.

mfranzen0906 commented 9 months ago

Nice I followed your advise with caching:

useEffect(() = >{
  if (objects.length > 0) {
    const obj = objects[objects.length - 1];
    const objRef = obj.ref;

    if (obj.type === "line") {
      let minX = obj.points[0];
      let minY = obj.points[1];
      let maxX = obj.points[0];
      let maxY = obj.points[1];

      for (let i = 2; i < obj.points.length; i += 2) {
        const x = obj.points[i];
        const y = obj.points[i + 1];

        if (x < minX) minX = x;
        if (y < minY) minY = y;
        if (x > maxX) maxX = x;
        if (y > maxY) maxY = y;
      }

      objRef.current.cache({
        width: maxX - minX + (obj.strokeWidth * 2),
        height: maxY - minY + (obj.strokeWidth * 2),
        pixelRatio: 1
      });
    } else {
      objRef.current.cache({
        pixelRatio: 1
      });
    }
  }
},
[objects]);

For now it seems to work. I had a big problem with memory exceeding on an iPad. But since I cache every line individually and calculate how big it is, it is better.

Is there a way to know how much space is left in the cache? Do you possibly know some improvement for the cache?

I could imagine something like: clear cache when a line is not visible and don't event render it and re-cache it again when it is visible again, but I couldn't make it work yet.