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.37k stars 916 forks source link

Artifacts when drawing lines with Firefox #1807

Open psychedelicious opened 1 month ago

psychedelicious commented 1 month ago

Occasionally on Firefox, when drawing lines, there are artifacts. Single pixels or a line of pixels are unexpectedly fully transparent. The issue is reproducible - if you destroy/recreate layer, change vsibility, or restart the browser, so long as the window and zoom and points/attrs don't change, the artifacts are identical.

If you cache the node, there are often similar artifacts except up in different places. Unfortunately these artifacts are "real" and appear in blobs or images created from the node.

The issue appears to be that the lineCap isn't drawn correctly. Issue only appears with specific combinations of:

Strangely, the problem seems to occur more when drawing towards the bottom-right corner of the page, but maybe this is placebo.

When using native canvas to draw, there is no such problem. I cannot reproduce the issue on Chromium browsers.

To reproduce the issue:

Below is a repro that works on my system, when the window is at the just the right size. A button changes rendering w/ Konva.Line to native context.

Here's a video, bc this probably won't reproduce the specific instance of the problem on your system. Red = draw with Konva.Line, green = draw with native context.

This can occur with any pixelRatio. In the video/repro, my pixelRatio is 1, matching window.devicePixelRatio.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/konva@8/konva.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
    <meta charset="utf-8" />
    <title>Konva Free Drawing Demo</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        background-color: #f0f0f0;
      }
    </style>
  </head>

  <body>
    <button id="draw-with-konva">Draw with Konva.Line</button>
    <button id="draw-with-native">Draw with Native</button>
    <div id="container"></div>
    <script>
      // These points reproduce the issue for my browser when window is at the right size etc
      const points = [
        1105, 863, 1105, 863, 1105, 863, 1104, 864, 1110, 870, 1126, 880, 1153, 891, 1208, 910,
        1313, 932, 1417, 946, 1473, 951, 1507, 952, 1524, 952, 1525, 952,
      ];

      console.log('window.devicePixelRatio', window.devicePixelRatio);
      console.log('Konva.pixelRatio', Konva.pixelRatio);

      let width = window.innerWidth;
      let height = window.innerHeight - 25;

      const stage = new Konva.Stage({
        container: 'container',
        width: width,
        height: height,
      });

      function drawWithKonvaLine(points) {
        // Draw the line w/ Konva.Line
        stage.destroyChildren();

        const layer = new Konva.Layer();

        stage.add(layer);

        layer.add(
          new Konva.Line({
            stroke: 'red',
            strokeWidth: 100,
            lineCap: 'round',
            lineJoin: 'round',
            points: [...points],
          })
        );
      }

      function drawWithNativeContext(points) {
        // Draw the line w/ native 2d context
        stage.destroyChildren();

        const layer = new Konva.Layer();
        stage.add(layer);

        // then we are going to draw into special canvas element
        const canvas = document.createElement('canvas');
        canvas.width = stage.width();
        canvas.height = stage.height();

        const image = new Konva.Image({
          image: canvas,
          x: 0,
          y: 0,
        });
        layer.add(image);

        // Chunk points for ease of drawing
        const chunkedPoints = _.chunk(points, 2);

        // Good. Now we need to get access to context element
        const context = canvas.getContext('2d');
        context.strokeStyle = 'green';
        context.lineJoin = 'round';
        context.lineCap = 'round';
        context.lineWidth = 100;

        context.beginPath();

        let lastPoint = chunkedPoints[0];
        for (const point of chunkedPoints.slice(1)) {
          context.moveTo(lastPoint[0], lastPoint[1]);
          context.lineTo(point[0], point[1]);
          context.stroke();
          lastPoint = point;
        }
        context.closePath();
        layer.batchDraw();
      }

      document.querySelector('#draw-with-konva').addEventListener('click', () => {
        drawWithKonvaLine(points);
      });

      document.querySelector('#draw-with-native').addEventListener('click', () => {
        drawWithNativeContext(points);
      });

      drawWithKonvaLine(points);
    </script>
  </body>
</html>
lavrton commented 5 days ago

I see the issue on my Mac with Firefox. But I don't think there is much we can do from Konva side. That looks like a Firefox bug. The only solution I can think of is drawing such a large stroke manually as a large blob (like a closed path that goes around stroke border).