Search code examples
javascriptfabricjs

How to change the image URL in fabric.js


After adding an image to my canvas (and letting fabric.js do it's thing), I want to change the URL of the image.

The issue is, it only part works. After I click a button (which makes the coding change), no image is shown. Just a place where an image was which I can still manipulate. When I manipulate the image, the image suddenly becomes visible. However, I'd like to see the image straight away.

As example

<button id="change">Change</button>
<canvas id="canvas" class="myStage" width="800" height="400"></canvas>

<script>      
 "use strict";
  const canvas = new fabric.Canvas("canvas", { containerClass: "myStage" });
  const changeBtn = document.getElementById("change");
  changeBtn.addEventListener("click", changeSelected);
  let _selectedInstrument = null;

  fabric.Image.fromURL("https://stackoverflow.design/assets/img/logos/teams/teams-logo-compact.svg", function (img) {
      canvas.add(img);
      img.on("mouseup", function (e) {
        _selectedInstrument = e;
      }
    );
  });
  
//this has the same result as the uncommented method beneath
//function changeSelected() {
//    fabric.util.loadImage(
//      "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.svg",
//      function (img) {
//        return _selectedInstrument.currentTarget.setElement(img);
//      }, this);
//}

function changeSelected() {
    _selectedInstrument.target._element.src =
      "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.svg";
    canvas.renderAll();
}

In order to use the demo in Fiddle, please click the "SO For Teams" logo first (to select it) and then click the change button. Then, drag the empty image and it will suddenly show the stackoverflow logo.

I would rather the image just changed without having to then move it.

I have tried 2 different approaches (as per my code above). I'm calling renderAll() as per the fabric.js docs

I'm not sure what I'm doing wrong


Solution

  • When setting the new image src, the browser has to load the image data. By calling canvas.renderAll() immediately, you are re-rendering the canvas while the new image still loads. Instead, add a "load" event listener to the created image and only re-render the canvas when the new image is ready.

    <button id="change">Change</button>
    <canvas id="canvas" class="myStage" width="800" height="400"></canvas>
    
    <script>      
      "use strict";
      const canvas = new fabric.Canvas("canvas", { containerClass: "myStage" });
      const changeBtn = document.getElementById("change");
      changeBtn.addEventListener("click", changeSelected);
      let _selectedInstrument = null;
    
      fabric.Image.fromURL("https://stackoverflow.design/assets/img/logos/teams/teams-logo-compact.svg", function (img) {
        canvas.add(img);
        img._element.addEventListener("load", canvas.renderAll.bind(canvas)); // <-- automatically re-renders the canvas when a new image is ready
        img.on("mouseup", function (e) {
          _selectedInstrument = e;
        });
      });
    
    
     function changeSelected() {
       _selectedInstrument.target._element.src =
      "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.svg";
       // canvas.renderAll(); <-- Removed, is called when the new image has been loaded
     }