0

I am trying to draw large number of nodes in React Konva based on this performance demo.

My code:


const ZoomTest = (props) => {
  let width = window.innerWidth;
  let height = window.innerHeight;
  const [rects, setRects] = useState([])
  const [node, setNode] = useState(null)

  function makeRects () {
      for (let x=0; x<10000; x++) {
        var color = colors[colorIndex++];
        if (colorIndex >= colors.length) {
          colorIndex = 0;
        }

        let randX = Math.random() * width;
        let randY = Math.random() * height;

        rects.push(<Circle
                    id={x}
                    x={randX}
                    y={randY}
                    width={20}
                    height={20}
                    fill={color}
                    stroke={'blue'}
                    strokeWidth={1}
                    shadowBlur={0}
                   />)
      }

      setRects(rects)
    }

  function getRects() {
    if (rects.length === 0)
      makeRects()

    return rects
  }

  function tooltip() {
    if (node === null)
      return null

    return <Label x={node.x} y={node.y} opacity={0.75}>
              <Tag fill={'black'} pointerDirection={'down'} pointerWidth={10} pointerHeight={10} lineJoin={'round'} shadowColor={'black'}
                    shadowBlur={10} shadowOffsetX={10} shadowOffsetY={10} shadowOpacity={0.2}/>
              <Text text={node.text} fill={'white'} fontSize={18} padding={5} />
          </Label>
  }

  function onMouseOver(evt) {
    var node = evt.target;
    if (node) {
      // update tooltip
      var mousePos = node.getStage().getPointerPosition();
      setNode({ x: mousePos.x, y: mousePos.y, text: node.id() })
      console.log('node id = ', node.id())
    }
  }

  return (
      <Stage
          width={window.innerWidth}
          height={window.innerHeight}
          draggable='false'
          onMouseOver={onMouseOver}
          onMouseMove={onMouseOver}
          onDragMove={onMouseOver}
      >
        <Layer>
            {getRects()}
        </Layer>
        <Layer>
            {tooltip()}
        </Layer>
      </Stage>

  )
}

The issue is that tooltip is very slow as compared to the demo. I think this is due to re-renders of the component on every mouse event and this is causing the lag.

Any idea how to make the react-konva performance similar to konvajs?

droidbee
  • 134
  • 2
  • 16

1 Answers1

1

There is nothing special about react-konva. It is just React performance. In your render function, you have a large list of elements. Every time your state changes (on every mouseover) the react have to compare old structure with the new one. As you have many elements, it takes time to React to check it.

I think there are many ways to solve the issue. As one solution you can just move large list into another component and use React.memo to tell React that you don't need to recheck the whole list every time.

import React from "react";
import { render } from "react-dom";
import { Stage, Layer, Circle, Label, Tag, Text } from "react-konva";

var colors = ["red", "orange", "cyan", "green", "blue", "purple"];

const Rects = React.memo(({ rects }) => {
  return (
    <React.Fragment>
      {rects.map(rect => (
        <Circle
          key={rect.id}
          id={rect.id}
          x={rect.x}
          y={rect.y}
          width={20}
          height={20}
          fill={rect.color}
          stroke={"blue"}
          strokeWidth={1}
          shadowBlur={0}
        />
      ))}
    </React.Fragment>
  );
});

function makeRects() {
  let width = window.innerWidth;
  let height = window.innerHeight;
  const rects = [];
  let colorIndex = 0;
  for (let x = 0; x < 10000; x++) {
    var color = colors[colorIndex++];
    if (colorIndex >= colors.length) {
      colorIndex = 0;
    }

    let randX = Math.random() * width;
    let randY = Math.random() * height;

    rects.push({ id: x, x: randX, y: randY, color });
  }

  return rects;
}

const INITIAL = makeRects();

const App = props => {
  const [rects, setRects] = React.useState(INITIAL);
  const [node, setNode] = React.useState(null);

  function tooltip() {
    if (node === null) return null;

    return (
      <Label x={node.x} y={node.y} opacity={0.75}>
        <Tag
          fill={"black"}
          pointerDirection={"down"}
          pointerWidth={10}
          pointerHeight={10}
          lineJoin={"round"}
          shadowColor={"black"}
          shadowBlur={10}
          shadowOffsetX={10}
          shadowOffsetY={10}
          shadowOpacity={0.2}
        />
        <Text text={node.text} fill={"white"} fontSize={18} padding={5} />
      </Label>
    );
  }

  function onMouseOver(evt) {
    var node = evt.target;
    if (node) {
      // update tooltip
      var mousePos = node.getStage().getPointerPosition();
      setNode({ x: mousePos.x, y: mousePos.y, text: node.id() });
      console.log("node id = ", node.id());
    }
  }

  return (
    <Stage
      width={window.innerWidth}
      height={window.innerHeight}
      draggable="false"
      onMouseOver={onMouseOver}
      onMouseMove={onMouseOver}
      onDragMove={onMouseOver}
    >
      <Layer>
        <Rects rects={rects} />
      </Layer>
      <Layer>{tooltip()}</Layer>
    </Stage>
  );
};
render(<App />, document.getElementById("root"));

https://codesandbox.io/s/react-konva-performance-on-many-elements-y680w?file=/src/index.js

lavrton
  • 18,973
  • 4
  • 30
  • 63
  • I encounter a similar issue than this. Say I want to select 10 nodes out of 10000 total and then move them by mouse. In this case I can't memo the 10000 nodes and everytime I want update the position of the 10 selected nodes all the 10000 will be re-rendered which is unexpected. How can I prevent this issue? – jayatubi Jul 14 '21 at 05:15
  • @jayatubi it is hard to give any recommendation here. It depends on many things, what exactly you draw, what is state of your application, what state library do you use, etc. – lavrton Jul 15 '21 at 05:12