Search code examples
javascriptcanvasfabricjs

Fabric.js remnants of old canvas remain


I want to display the content of a canvas as the background of another canvas and draw a bunch of rectangles on there. I need to dynamically change:

  • the canvas from which to load the background image for my finalcanvas
  • which rectangles to draw

It's easiest if I start this process from scratch. I have imitated starting from scratch with a simple button. However, after redrawing my canvas, previous information from fabric.js remains present after dragging an item of a canvas a bit. This means that the old canvas was not cleared properly. I tried playing around with .clear() and .depose(), but to no avail.

In case the description is vague, here an image:

enter image description here

And an small reproducible example:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Hello</title>
</head>

<body>
  <canvas id="finalcanvas"></canvas>
  <canvas id="backgroundcanvas" width="200" height="100"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js" type="text/javascript"></script>
  <script>
    function load_plane_onto_active_canvas() {
      var c = document.getElementById('backgroundcanvas');
      var ctx = c.getContext("2d");
      var bg = c.toDataURL("image/png");

      var canvas = new fabric.Canvas('finalcanvas', {
        width: 333,
        height: 333
      });

      canvas.setBackgroundImage(bg, canvas.renderAll.bind(canvas));

      canvas.on("mouse:over", function(e) {
        console.log(e.target)
      });

      // add 100 rectangles
      for (i = 0; i < 10; i++) {
        for (j = 0; j < 10; j++) {
          rect = new fabric.Rect({
            width: 10,
            height: 10,
            left: j * 15,
            top: i * 15,
            fill: 'green',
          })
          canvas.add(rect);
        }
      }
    }

    window.onload = function() {
      // fill the background canvas with red
      (function() {
        var canvas = document.getElementById("backgroundcanvas");
        var ctx = canvas.getContext("2d");
        ctx.fillStyle = "#FF0000";
        ctx.fillRect(0, 0, 150, 75);
      }())

      load_plane_onto_active_canvas()
    }
  </script>
  <button onclick="load_plane_onto_active_canvas()">click me</button>

</body>

</html>

I hope someone can help me!


Solution

  • Notice that you're creating a new instance of fabric.Canvas in your load_plane_onto_active_canvas() function. So when you're clicking the button, the existing canvases stay intact, and you're actually calling clear() on your freshly created canvas. The DOM becomes messed up at this point, with several nested canvases inside of each other - you can take a peek at the DOM inspector to see that.

    What you can do instead is create the canvas instance just once, then work with a reference to it later in your other functions.

    const finalCanvas = new fabric.Canvas("finalcanvas", {
      width: 333,
      height: 333
    });
    // finalCanvas now exists in the global (window) scope
    
    function load_plane_onto_active_canvas() {
      var c = document.getElementById("backgroundcanvas");
      var ctx = c.getContext("2d");
      var bg = c.toDataURL("image/png");
      
      finalCanvas.clear();
    
      finalCanvas.setBackgroundImage(bg, finalCanvas.renderAll.bind(finalCanvas));
    
      finalCanvas.on("mouse:over", function (e) {
        // console.log(e.target);
      });
    
      // add 100 rectangles
      for (i = 0; i < 10; i++) {
        for (j = 0; j < 10; j++) {
          rect = new fabric.Rect({
            width: 10,
            height: 10,
            left: j * 15,
            top: i * 15,
            fill: "green"
          });
          finalCanvas.add(rect);
        }
      }
    }
    
    window.onload = function () {
      // fill the background canvas with red
      (function () {
        var canvas = document.getElementById("backgroundcanvas");
        var ctx = canvas.getContext("2d");
        ctx.fillStyle = "#FF0000";
        ctx.fillRect(0, 0, 150, 75);
      })();
    
      load_plane_onto_active_canvas();
    };
    document.querySelector("#b1").onclick = () => load_plane_onto_active_canvas();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.js"></script>
    <canvas id="finalcanvas"></canvas>
    <canvas id="backgroundcanvas" width="200" height="100"></canvas>
    <button id="b1">start from scratch</button>