Search code examples
javascriptfabricjs

How can I rename an object and render canvas in FabricJS?


I am using FabricJS 5 and have implemented a simple display for my layers.

When I press the pencil button I open a prompt and rename the layer using the following function:

function RenameLayer(name) {
  let layerObj = canvas.getItemByAttr("name", name);
  let newName = prompt("Rename Layer?", layerObj.name);

  while (checkIfNameExists(newName)) {
    alert("Your layer name: " + layerObj.name + " is NOT unique please rename your layer differently.")
    newName = prompt("Rename Layer?", layerObj.name);
  }

  layerObj.name = newName;
  canvas.renderAll();
}

function checkIfNameExists(newName) {
  count = 0;

  canvas.forEachObject(function(obj, i) {
    if (obj.name == newName) {
      count++;
    }
  });

  if (count > 0) {
    return true;
  } else {
    return false;
  }
}

However, after renaming I get an error.

Find below my example:

<html lang="en">

<head>
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css">

  <style>
    html,
    body {
      height: 100%;
      width: 100%;
      padding: 0;
      margin: 0;
    }
    
    body {
      touch-action: none;
      background-image: linear-gradient(to bottom left, rgb(214, 240, 201) 10%, rgba(255, 231, 191, 1) 80%);
      -webkit-user-select: none;
      -moz-user-select: -moz-none;
      -ms-user-select: none;
      user-select: none;
    }
    
    .input-group {
      padding: 4px;
    }
    
    .canvas-container {
      margin: 0 auto;
      width: 100%;
      overflow: hidden;
      background: url(./transparent.png);
      background-size: 15px 15px;
      box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
    }
    
    .actived {
      background: #fff9a8;
    }
    
    #sortable {
      max-height: 200px;
      overflow: scroll;
    }
    
    .list-group {
      line-height: 35px;
    }
    
    .svg-icon {
      width: 1em;
      height: 1em;
    }
    
    .svg-icon path,
    .svg-icon polygon,
    .svg-icon rect {
      fill: #4691f6;
    }
    
    .svg-icon circle {
      stroke: #4691f6;
      stroke-width: 1;
    }
    
    #bkboxXX {
      background: url(transparent.png);
      border: 1px solid rgba(0, 0, 0, 0.3);
      display: inline-block;
      height: 19px;
      width: 20px;
    }
    
    .bk-btn {
      background: url(/transparent.png);
    }
    
    .fl {
      float: left;
    }
    
    .fr {
      float: right;
    }
    
    .input-group-text {
      background-color: #f4f4f4;
    }
    
    .input-group-sm>.input-group-text {
      /*
            padding: .2rem .3rem !important;
            */
    }
    
    .list-group-item {
      padding: 2px 10px;
      ;
      cursor: pointer;
    }
    
    .list-group {
      border-radius: 0;
      text-align: left;
    }
  </style>
</head>

<body>
  <div class="container-fluid h-100">
    <div class="row h-100">
      <div class="col-sm-3 border-end text-center h-100 overflow-scroll bg-light py-3">
        <div class="form-floating w-100 mb-3 tour5">
          <h6 class="mb-3">Add Element</h6>
          <button class="btn btn-outline-primary" onclick="addText();" data-toggle="tooltip" data-placement="top" title="" data-bs-original-title="Add Text" aria-label="Add Text"><i
                            class="fa fa-font"></i></button>

          <button class="btn btn-outline-primary" data-toggle="tooltip" data-placement="top" title="" onclick="  canvas.add(new fabric.Circle({ name: genNextName(), originX:'center', originY:'center',  radius: 30, fill: 'green', top: 100, left: 100 }));" data-bs-original-title="Add Circle"
            aria-label="Add Circle"><i
                            class="fa fa-circle"></i></button>
          <button class="btn btn-outline-primary" data-toggle="tooltip" data-placement="top" title="" onclick="  canvas.add(new fabric.Triangle({  name: genNextName(),  originX:'center', originY:'center', height:100, width:100, fill: 'red', top: 100, left: 100 }));"
            data-bs-original-title="Add Triangle">
                        <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="triangle" role="img"
                            xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" class=""
                            style="height: 16px;top: 9px;">
                            <path fill="currentColor"
                                d="M329.6 24c-18.4-32-64.7-32-83.2 0L6.5 440c-18.4 31.9 4.6 72 41.6 72H528c36.9 0 60-40 41.6-72l-240-416z"
                                class=""></path>
                        </svg>
                    </button>
          <button class="btn btn-outline-primary" data-toggle="tooltip" data-placement="top" title="" onclick="canvas.add(new fabric.Rect({ name: genNextName(),  originX:'center', originY:'center', left: 110, top: 110, fill: '#000', width: 50, height: 50 }))" data-bs-original-title="Add Rectangle"><i class="fa fa-square-full"></i> </button>
        </div>
        <hr>
        <div class="w-100  tour7 Xd-none Xd-sm-block">
          <h6 class="mb-3">Elements</h6>
          <ul class="list-group" id="sortable"></ul>
        </div>
      </div>
      <div class="col-sm-6 my-auto py-4 overflow-hidden">
        <div class="canvas-container" style="width: 329px; height: 329px; position: relative; user-select: none;"><canvas id="c" class="lower-canvas" width="329" height="329" style="position: absolute; width: 329px; height: 329px; left: 0px; top: 0px; touch-action: none; user-select: none;"></canvas>
        </div>
      </div>
      <div class="col-sm-3 border-start py-3 h-100 overflow-scroll bg-light text-center">
        right map
      </div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
  <script src="https://code.jquery.com/jquery-3.6.0.js"></script>
  <script src="https://code.jquery.com/ui/1.13.1/jquery-ui.js"></script>
  <script src="https://unpkg.com/[email protected]/dist/fabric.min.js"></script>
  <script src="https://rawcdn.githack.com/lyzerk/fabric-history/8c223cbdc8305307b4a8f8710f97da54d9146ffa/src/index.js"></script>
  <script src="https://rawgit.com/fabricjs/fabric.js/master/lib/centering_guidelines.js"></script>
  <script src="https://rawgit.com/fabricjs/fabric.js/master/lib/aligning_guidelines.js"></script>

  <script src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.10/webfont.js"></script>

  <script>
    const rgba2hex = (rgba) => `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`

    function cn2h(cn) {
      if (!cn) {
        return 'transparent';
      }
      d = document.createElement("span");
      d.style.display = "none";
      d.style.color = cn
      document.body.appendChild(d)
      return rgba2hex(window.getComputedStyle(d).color);
    }
  </script>
  <script>
    var canvas;
    var apiUrl;
    var startedLoadingFamilies = false;
    var to;
    var scale;

    fabric.Text.prototype.set({
      _getNonTransformedDimensions() { // Object dimensions
        return new fabric.Point(this.width, this.height).scalarAdd(this.padding);
      },
      _calculateCurrentDimensions() { // Controls dimensions
        return fabric.util.transformPoint(this._getTransformedDimensions(), this.getViewportTransform(), true);
      }
    });

    function fitCanvas(w, h) {
      var scaleW = (document.querySelectorAll(".col-sm-6")[0].offsetWidth - 100) / w;
      var scaleH = (document.querySelectorAll("body")[0].offsetHeight - 100) / h;
      scale = Math.min(scaleH, scaleW);

      if (scale > 1) {
        scale = 1;
      }


      canvas.setZoom(scale);
      canvas.setWidth(w * scale);
      canvas.setHeight(h * scale);

      initAligningGuidelines(canvas);
      initCenteringGuidelines(canvas);

      canvas.renderAll();
      console.log("finished!!");

    }

    (function() {
      canvas = new fabric.Canvas('c', {
        allowTouchScrolling: true
      });
      WebFont.load({
        google: {
          families: ["Inter:200"]
        },
        active: function() {
          canvas.loadFromJSON({
            "height": "500",
            "width": "500",
            "edit": "yes",
            "objects": [],
            "backgroundImage": {
              "crossOrigin": "anonymous"
            }
          }, function() {
            fitCanvas(500, 500);
          })
        }
      });

      canvas.on('before:render', function(opt) {
        canvas.getObjects().map(function(o, i) {
          if (o.type.toLowerCase() == "textbox") {
            var maxH = (o.maxHeight) ? (o.maxHeight) : (canvas.height);
            o.set({
              'maxHeight': maxH
            });
            if (o.fontSize > 0 && (maxH > o.fontSize)) {
              while (o.height > maxH) {
                o.set({
                  'fontSize': o.fontSize - 1
                });
              }
            }
          }

          if (o.circleFrame) {
            o.set({
              clipPath: new fabric.Circle({
                radius: o.width / 2,
                originX: 'center',
                originY: 'center'
              })
            });

          }

          if ((o.rx || o.ry) && o.type == "image") {

            let rx = (o.rx || 0);
            let ry = (o.ry || 0);

            o.set({
              clipPath: new fabric.Rect({
                rx: rx,
                ry: ry,
                height: o.height,
                width: o.width,
                originX: 'center',
                originY: 'center'
              })
            });
          }
        });
      });
    })();
  </script>

  <script>
    fabric.Object.prototype.objectCaching = false;

    function setCanvasBG(clr) {
      canvas.backgroundColor = clr;
      canvas.renderAll();
    }

    function changeDim() {
      fitCanvas($("#widthC").val(), $("#heightC").val());
    }

    function setColor(clr, what = 'fill') {
      canvas.getActiveObject().set(what, clr);
      canvas.renderAll();
    }

    (function() {

      $(document).on("mouseenter", "[data-label]", function() {
        $("#font-search").val($(this).data("label"));
        loadFont($(this).data("label"));
      });

      fabric.Canvas.prototype.getItemByAttr = function(attr, name) {
        var object = null,
          objects = this.getObjects();
        for (var i = 0, len = this.size(); i < len; i++) {
          if (objects[i][attr] && objects[i][attr] == name) {
            object = objects[i];
            break;
          }
        }
        return object;
      };

      $("form#f").hide();

      var activeObject;

      $("#f input, #f select").on("input", function() {
        if (["height", "width", "top", "left", "strokeWidth", "charSpacing"].includes(this.name)) {
          canvas.getActiveObject().set(this.name, parseFloat(this.value)).setCoords();

        } else {
          canvas.getActiveObject().set(this.name, this.value).setCoords();
        }
        canvas.renderAll();
      });

      canvas.preserveObjectStacking = true;

      fabric.Object.prototype.toObject = (function(toObject) {
        return function() {
          return fabric.util.object.extend(toObject.call(this), {
            name: this.name,
            text: this.text,
            textAlign: this.textAlign,
            fontSize: this.fontSize,
            charSpacing: this.charSpacing,
            lineHeight: this.lineHeight,
            fontWeight: this.fontWeight,
            fontFamily: this.fontFamily,
            fontStyle: this.fontStyle,
            textBackgroundColor: this.textBackgroundColor,
            originX: this.originX,
            originY: this.originY,
            maxHeight: this.maxHeight,
            height: this.height,
            width: this.width,
            radius: this.radius,
            rx: this.rx,
            ry: this.ry,
            stroke: this.stroke,
            padding: this.padding,
            circleFrame: this.circleFrame
          });
        };
      })(fabric.Object.prototype.toObject);

      canvas.on('object:scaling', function(e) {
        if (e.target.toObject().type != "image" && e.target.toObject().type != "circle") {
          e.target.set({
            width: e.target.width * e.target.scaleX,
            height: e.target.height * e.target.scaleY,
            scaleX: 1,
            scaleY: 1
          })
        }
      });

      canvas.on('object:modified', function(opt) {
        document.body.style.cursor = 'progress';
      });

      canvas.on("after:render", function() {

        $("#sortable").empty();

        canvas.includeDefaultValues = false;
        canvas.toObject().objects.forEach(function(layer, id) {

          if (typeof layer.name !== 'undefined') {
            //console.log(id,layer.name);
            //canvas.getItemByAttr(`name`, layer.name).set({ "name": String.fromCharCode(65 + id).toLowerCase() })
            canvas.getItemByAttr(`name`, layer.name).set({
              "name": layer.name.replace(/ /g, "_")
            })
            var actived = '';
            if (canvas.getActiveObject()) {
              actived = (canvas.getActiveObject().name == layer.name) ? " actived" : "";
            }

            $("#sortable").append('<li class="list-group-item ui-sortable-handle clearfix ' + actived + '" onClick="canvas.setActiveObject(canvas.getItemByAttr(`name`,`' + layer.name + '`)); canvas.renderAll();" id=' + layer.name + '>' + layer.name + ' (' + layer.type + ')' + '<span class="fr button-group"><a class="mx-2" title="Edit" href="#" onClick="RenameLayer(`' + layer.name + '`)"><i class="fa fa-pencil-alt" aria-hidden="true"></i></a><a class="mx-2" title="Delete" href="#" onClick="if(confirm(`Are you sure?`)){canvas.remove(canvas.getItemByAttr(`name`,`' + layer.name + '`)); canvas.renderAll();}"><i class="fa fa-trash-alt" aria-hidden="true"></i></a><a class="me-2"  title="Duplicate this layer" href="#" onClick="Duplicate(`' + layer.name + '`); canvas.renderAll();"><i class="fa fa-clone" aria-hidden="true"></i></a></span></li>');
          }
        });
        document.body.style.cursor = 'default'
      });

      canvas.on("selection:created", function(obj) {
        if ("image" == obj.selected[0].type) {
          canvas.getActiveObject().setControlsVisibility({
            mb: false,
            ml: false,
            mt: false,
            mr: false
          });
        }
        $("form#f input[type!='hidden'], #f select").parent().hide();
        $("form#f").hide();
        canvas.renderAll();
      });

      canvas.on("selection:updated", function(obj) {
        if ("image" == obj.selected[0].type) {
          canvas.getActiveObject().setControlsVisibility({
            mb: false,
            ml: false,
            mt: false,
            mr: false
          });
        }
        canvas.renderAll();
        $("form#f").hide();
        $("form#f input[type!='hidden'] , #f select, #f textarea").parent().hide();
      });

      canvas.on("selection:cleared", function() {
        canvas.renderAll();
        $("form#f").hide();
        $("form#f input[type!='hidden'], #f select, #f textarea").parent().hide();
      });

      canvas.hoverCursor = 'default';
      canvas.on('mouse:over', function(e) {
        if (e.target) {
          e.target._renderControls(canvas.contextTop, {
            hasControls: false
          })
        }
      });

      canvas.on('mouse:out', function(e) {
        canvas.clearContext(canvas.contextTop);
      });
      canvas.on('mouse:down', function(e) {
        canvas.clearContext(canvas.contextTop);
      });

      addText = function() {
        var text = new fabric.Textbox("Edit this Text", {
          name: genNextName(),
          left: canvas.getWidth() / canvas.getZoom() / 2,
          top: canvas.getHeight() / canvas.getZoom() / 2,
          width: (canvas.getWidth() / canvas.getZoom()) / 2,
          fill: "#000000",
          originX: "center",
          originY: "center",
          fontFamily: "Inter",
          fontWeight: 400,
          fontSize: 60,
          padding: 20
        });
        text.setControlsVisibility({
          mt: false,
          mb: false,
          ml: true,
          mr: true,
          tl: true,
          tr: true,
          bl: true,
          br: true
        });

        canvas.add(text);
        canvas.setActiveObject(text);
        canvas.renderAll();
      };
    })();

    function hide_mh_box() {
      canvas.remove(canvas.getItemByAttr("isBB", true));
    }

    function show_mh_box() {
      var h = parseInt($("[name=maxHeight]").val());
      var n = new fabric.Rect({
        top: canvas.getActiveObject().get('top'),
        left: canvas.getActiveObject().get('left'),
        width: canvas.getActiveObject().get('width'),
        height: h,
        originX: canvas.getActiveObject().get('originX'),
        originY: canvas.getActiveObject().get('originY'),
        angle: canvas.getActiveObject().get('angle'),
        opacity: 1,
        strokeWidth: 2,
        stroke: "#FF00FF",
        fill: "rgba(0,0,0,0)",
        evented: !1,
        isBB: true
      });
      canvas.add(n);
      canvas.renderAll();
    }

    var visited = [];

    $(document).ready(function() {

      $("input[type=color]").on("input", function() {
        $(this).parent().parent().find('input').first().val(this.value);
      });

      $("textarea, input").attr("autocomplete", "off");

      if (window !== window.parent) {
        const url = new URL(document.referrer);
        $(".isIframe").removeClass('d-none');
        $(".notIframe").hide();
      }
    });
  </script>

  <script>
    function Duplicate(name) {
      Copy(name);
    }

    function Copy(name) {
      canvas.getItemByAttr("name", name).clone(function(cloned) {
        cloned.set({
          "name": genNextName()
        })
        _clipboard = cloned;
        Paste(name);
        canvas.renderAll();
      });
    }

    function RenameLayer(name) {
      let layerObj = canvas.getItemByAttr("name", name);

      let newName = prompt("Rename Layer?", layerObj.name);

      while (checkIfNameExists(newName)) {
        alert("Your layer name: " + layerObj.name + " is NOT unique please rename your layer differently.")
        newName = prompt("Rename Layer?", layerObj.name);
      }

      layerObj.name = newName;

      canvas.renderAll();
    }

    function checkIfNameExists(newName) {
      count = 0;

      canvas.forEachObject(function(obj, i) {
        if (obj.name == newName) {
          count++;
        }
      });

      if (count > 0) {
        return true;
      } else {
        return false;
      }
    }

    function Paste(name) {
      _clipboard.clone(function(clonedObj) {
        canvas.discardActiveObject();
        clonedObj.set({
          left: clonedObj.left + 10,
          top: clonedObj.top + 10,
          evented: true,
        });
        if (clonedObj.type === 'activeSelection') {
          clonedObj.canvas = canvas;
          clonedObj.forEachObject(function(obj) {
            canvas.add(obj);
          });
          clonedObj.setCoords();
        } else {
          canvas.add(clonedObj);
        }
        _clipboard.top += 10;
        _clipboard.left += 10;
        canvas.setActiveObject(clonedObj);
        canvas.requestRenderAll();
      });
    }

    function genNextName() {
      canvas.renderAll();
      var total = canvas.getObjects().length;
      return String.fromCharCode(65 + total).toLowerCase()
    }

    fabric.Object.prototype.transparentCorners = false;
    fabric.Object.prototype.borderColor = '#5cdce4';
    fabric.Object.prototype.cornerColor = 'white';
    fabric.Object.prototype.cornerStrokeColor = 'blue';
    fabric.Object.prototype.cornerStyle = 'circle';
    fabric.Object.prototype.cornerSize = 10;
    fabric.Object.prototype.borderScaleFactor = 2;
  </script>
</body>

</html>

enter image description here

Any suggestions what I am doing wrong?

I appreciate your replies!


Solution

  • I didn't do deep debugging of the code, but since in many places the layer "name" is used as a key value, you can try renaming the layer (and subsequent updating) in a separate macrotask, using setTimeout.

    Instead of this code:

    layerObj.name = newName;
    canvas.renderAll();
    

    try this code:

    setTimeout(() => {
      layerObj.name = newName;
      canvas.renderAll();
    });
    

    Example of your code with setTimeout:

    <html lang="en">
    
    <head>
      <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css" />
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css">
    
      <style>
        html,
        body {
          height: 100%;
          width: 100%;
          padding: 0;
          margin: 0;
        }
        
        body {
          touch-action: none;
          background-image: linear-gradient(to bottom left, rgb(214, 240, 201) 10%, rgba(255, 231, 191, 1) 80%);
          -webkit-user-select: none;
          -moz-user-select: -moz-none;
          -ms-user-select: none;
          user-select: none;
        }
        
        .input-group {
          padding: 4px;
        }
        
        .canvas-container {
          margin: 0 auto;
          width: 100%;
          overflow: hidden;
          background: url(./transparent.png);
          background-size: 15px 15px;
          box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
        }
        
        .actived {
          background: #fff9a8;
        }
        
        #sortable {
          max-height: 200px;
          overflow: scroll;
        }
        
        .list-group {
          line-height: 35px;
        }
        
        .svg-icon {
          width: 1em;
          height: 1em;
        }
        
        .svg-icon path,
        .svg-icon polygon,
        .svg-icon rect {
          fill: #4691f6;
        }
        
        .svg-icon circle {
          stroke: #4691f6;
          stroke-width: 1;
        }
        
        #bkboxXX {
          background: url(transparent.png);
          border: 1px solid rgba(0, 0, 0, 0.3);
          display: inline-block;
          height: 19px;
          width: 20px;
        }
        
        .bk-btn {
          background: url(/transparent.png);
        }
        
        .fl {
          float: left;
        }
        
        .fr {
          float: right;
        }
        
        .input-group-text {
          background-color: #f4f4f4;
        }
        
        .input-group-sm>.input-group-text {
          /*
                padding: .2rem .3rem !important;
                */
        }
        
        .list-group-item {
          padding: 2px 10px;
          ;
          cursor: pointer;
        }
        
        .list-group {
          border-radius: 0;
          text-align: left;
        }
      </style>
    </head>
    
    <body>
      <div class="container-fluid h-100">
        <div class="row h-100">
          <div class="col-sm-3 border-end text-center h-100 overflow-scroll bg-light py-3">
            <div class="form-floating w-100 mb-3 tour5">
              <h6 class="mb-3">Add Element</h6>
              <button class="btn btn-outline-primary" onclick="addText();" data-toggle="tooltip" data-placement="top" title="" data-bs-original-title="Add Text" aria-label="Add Text"><i
                                class="fa fa-font"></i></button>
    
              <button class="btn btn-outline-primary" data-toggle="tooltip" data-placement="top" title="" onclick="  canvas.add(new fabric.Circle({ name: genNextName(), originX:'center', originY:'center',  radius: 30, fill: 'green', top: 100, left: 100 }));" data-bs-original-title="Add Circle"
                aria-label="Add Circle"><i
                                class="fa fa-circle"></i></button>
              <button class="btn btn-outline-primary" data-toggle="tooltip" data-placement="top" title="" onclick="  canvas.add(new fabric.Triangle({  name: genNextName(),  originX:'center', originY:'center', height:100, width:100, fill: 'red', top: 100, left: 100 }));"
                data-bs-original-title="Add Triangle">
                            <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="triangle" role="img"
                                xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" class=""
                                style="height: 16px;top: 9px;">
                                <path fill="currentColor"
                                    d="M329.6 24c-18.4-32-64.7-32-83.2 0L6.5 440c-18.4 31.9 4.6 72 41.6 72H528c36.9 0 60-40 41.6-72l-240-416z"
                                    class=""></path>
                            </svg>
                        </button>
              <button class="btn btn-outline-primary" data-toggle="tooltip" data-placement="top" title="" onclick="canvas.add(new fabric.Rect({ name: genNextName(),  originX:'center', originY:'center', left: 110, top: 110, fill: '#000', width: 50, height: 50 }))" data-bs-original-title="Add Rectangle"><i class="fa fa-square-full"></i> </button>
            </div>
            <hr>
            <div class="w-100  tour7 Xd-none Xd-sm-block">
              <h6 class="mb-3">Elements</h6>
              <ul class="list-group" id="sortable"></ul>
            </div>
          </div>
          <div class="col-sm-6 my-auto py-4 overflow-hidden">
            <div class="canvas-container" style="width: 329px; height: 329px; position: relative; user-select: none;"><canvas id="c" class="lower-canvas" width="329" height="329" style="position: absolute; width: 329px; height: 329px; left: 0px; top: 0px; touch-action: none; user-select: none;"></canvas>
            </div>
          </div>
          <div class="col-sm-3 border-start py-3 h-100 overflow-scroll bg-light text-center">
            right map
          </div>
        </div>
      </div>
    
      <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
      <script src="https://code.jquery.com/jquery-3.6.0.js"></script>
      <script src="https://code.jquery.com/ui/1.13.1/jquery-ui.js"></script>
      <script src="https://unpkg.com/[email protected]/dist/fabric.min.js"></script>
      <script src="https://rawcdn.githack.com/lyzerk/fabric-history/8c223cbdc8305307b4a8f8710f97da54d9146ffa/src/index.js"></script>
      <script src="https://rawgit.com/fabricjs/fabric.js/master/lib/centering_guidelines.js"></script>
      <script src="https://rawgit.com/fabricjs/fabric.js/master/lib/aligning_guidelines.js"></script>
    
      <script src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.10/webfont.js"></script>
    
      <script>
        const rgba2hex = (rgba) => `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`
    
        function cn2h(cn) {
          if (!cn) {
            return 'transparent';
          }
          d = document.createElement("span");
          d.style.display = "none";
          d.style.color = cn
          document.body.appendChild(d)
          return rgba2hex(window.getComputedStyle(d).color);
        }
      </script>
      <script>
        var canvas;
        var apiUrl;
        var startedLoadingFamilies = false;
        var to;
        var scale;
    
        fabric.Text.prototype.set({
          _getNonTransformedDimensions() { // Object dimensions
            return new fabric.Point(this.width, this.height).scalarAdd(this.padding);
          },
          _calculateCurrentDimensions() { // Controls dimensions
            return fabric.util.transformPoint(this._getTransformedDimensions(), this.getViewportTransform(), true);
          }
        });
    
        function fitCanvas(w, h) {
          var scaleW = (document.querySelectorAll(".col-sm-6")[0].offsetWidth - 100) / w;
          var scaleH = (document.querySelectorAll("body")[0].offsetHeight - 100) / h;
          scale = Math.min(scaleH, scaleW);
    
          if (scale > 1) {
            scale = 1;
          }
    
    
          canvas.setZoom(scale);
          canvas.setWidth(w * scale);
          canvas.setHeight(h * scale);
    
          initAligningGuidelines(canvas);
          initCenteringGuidelines(canvas);
    
          canvas.renderAll();
          console.log("finished!!");
    
        }
    
        (function() {
          canvas = new fabric.Canvas('c', {
            allowTouchScrolling: true
          });
          WebFont.load({
            google: {
              families: ["Inter:200"]
            },
            active: function() {
              canvas.loadFromJSON({
                "height": "500",
                "width": "500",
                "edit": "yes",
                "objects": [],
                "backgroundImage": {
                  "crossOrigin": "anonymous"
                }
              }, function() {
                fitCanvas(500, 500);
              })
            }
          });
    
          canvas.on('before:render', function(opt) {
            canvas.getObjects().map(function(o, i) {
              if (o.type.toLowerCase() == "textbox") {
                var maxH = (o.maxHeight) ? (o.maxHeight) : (canvas.height);
                o.set({
                  'maxHeight': maxH
                });
                if (o.fontSize > 0 && (maxH > o.fontSize)) {
                  while (o.height > maxH) {
                    o.set({
                      'fontSize': o.fontSize - 1
                    });
                  }
                }
              }
    
              if (o.circleFrame) {
                o.set({
                  clipPath: new fabric.Circle({
                    radius: o.width / 2,
                    originX: 'center',
                    originY: 'center'
                  })
                });
    
              }
    
              if ((o.rx || o.ry) && o.type == "image") {
    
                let rx = (o.rx || 0);
                let ry = (o.ry || 0);
    
                o.set({
                  clipPath: new fabric.Rect({
                    rx: rx,
                    ry: ry,
                    height: o.height,
                    width: o.width,
                    originX: 'center',
                    originY: 'center'
                  })
                });
              }
            });
          });
        })();
      </script>
    
      <script>
        fabric.Object.prototype.objectCaching = false;
    
        function setCanvasBG(clr) {
          canvas.backgroundColor = clr;
          canvas.renderAll();
        }
    
        function changeDim() {
          fitCanvas($("#widthC").val(), $("#heightC").val());
        }
    
        function setColor(clr, what = 'fill') {
          canvas.getActiveObject().set(what, clr);
          canvas.renderAll();
        }
    
        (function() {
    
          $(document).on("mouseenter", "[data-label]", function() {
            $("#font-search").val($(this).data("label"));
            loadFont($(this).data("label"));
          });
    
          fabric.Canvas.prototype.getItemByAttr = function(attr, name) {
            var object = null,
              objects = this.getObjects();
            for (var i = 0, len = this.size(); i < len; i++) {
              if (objects[i][attr] && objects[i][attr] == name) {
                object = objects[i];
                break;
              }
            }
            return object;
          };
    
          $("form#f").hide();
    
          var activeObject;
    
          $("#f input, #f select").on("input", function() {
            if (["height", "width", "top", "left", "strokeWidth", "charSpacing"].includes(this.name)) {
              canvas.getActiveObject().set(this.name, parseFloat(this.value)).setCoords();
    
            } else {
              canvas.getActiveObject().set(this.name, this.value).setCoords();
            }
            canvas.renderAll();
          });
    
          canvas.preserveObjectStacking = true;
    
          fabric.Object.prototype.toObject = (function(toObject) {
            return function() {
              return fabric.util.object.extend(toObject.call(this), {
                name: this.name,
                text: this.text,
                textAlign: this.textAlign,
                fontSize: this.fontSize,
                charSpacing: this.charSpacing,
                lineHeight: this.lineHeight,
                fontWeight: this.fontWeight,
                fontFamily: this.fontFamily,
                fontStyle: this.fontStyle,
                textBackgroundColor: this.textBackgroundColor,
                originX: this.originX,
                originY: this.originY,
                maxHeight: this.maxHeight,
                height: this.height,
                width: this.width,
                radius: this.radius,
                rx: this.rx,
                ry: this.ry,
                stroke: this.stroke,
                padding: this.padding,
                circleFrame: this.circleFrame
              });
            };
          })(fabric.Object.prototype.toObject);
    
          canvas.on('object:scaling', function(e) {
            if (e.target.toObject().type != "image" && e.target.toObject().type != "circle") {
              e.target.set({
                width: e.target.width * e.target.scaleX,
                height: e.target.height * e.target.scaleY,
                scaleX: 1,
                scaleY: 1
              })
            }
          });
    
          canvas.on('object:modified', function(opt) {
            document.body.style.cursor = 'progress';
          });
    
          canvas.on("after:render", function() {
    
            $("#sortable").empty();
    
            canvas.includeDefaultValues = false;
            canvas.toObject().objects.forEach(function(layer, id) {
    
              if (typeof layer.name !== 'undefined') {
                //console.log(id,layer.name);
                //canvas.getItemByAttr(`name`, layer.name).set({ "name": String.fromCharCode(65 + id).toLowerCase() })
                canvas.getItemByAttr(`name`, layer.name).set({
                  "name": layer.name.replace(/ /g, "_")
                })
                var actived = '';
                if (canvas.getActiveObject()) {
                  actived = (canvas.getActiveObject().name == layer.name) ? " actived" : "";
                }
    
                $("#sortable").append('<li class="list-group-item ui-sortable-handle clearfix ' + actived + '" onClick="canvas.setActiveObject(canvas.getItemByAttr(`name`,`' + layer.name + '`)); canvas.renderAll();" id=' + layer.name + '>' + layer.name + ' (' + layer.type + ')' + '<span class="fr button-group"><a class="mx-2" title="Edit" href="#" onClick="RenameLayer(`' + layer.name + '`)"><i class="fa fa-pencil-alt" aria-hidden="true"></i></a><a class="mx-2" title="Delete" href="#" onClick="if(confirm(`Are you sure?`)){canvas.remove(canvas.getItemByAttr(`name`,`' + layer.name + '`)); canvas.renderAll();}"><i class="fa fa-trash-alt" aria-hidden="true"></i></a><a class="me-2"  title="Duplicate this layer" href="#" onClick="Duplicate(`' + layer.name + '`); canvas.renderAll();"><i class="fa fa-clone" aria-hidden="true"></i></a></span></li>');
              }
            });
            document.body.style.cursor = 'default'
          });
    
          canvas.on("selection:created", function(obj) {
            if ("image" == obj.selected[0].type) {
              canvas.getActiveObject().setControlsVisibility({
                mb: false,
                ml: false,
                mt: false,
                mr: false
              });
            }
            $("form#f input[type!='hidden'], #f select").parent().hide();
            $("form#f").hide();
            canvas.renderAll();
          });
    
          canvas.on("selection:updated", function(obj) {
            if ("image" == obj.selected[0].type) {
              canvas.getActiveObject().setControlsVisibility({
                mb: false,
                ml: false,
                mt: false,
                mr: false
              });
            }
            canvas.renderAll();
            $("form#f").hide();
            $("form#f input[type!='hidden'] , #f select, #f textarea").parent().hide();
          });
    
          canvas.on("selection:cleared", function() {
            canvas.renderAll();
            $("form#f").hide();
            $("form#f input[type!='hidden'], #f select, #f textarea").parent().hide();
          });
    
          canvas.hoverCursor = 'default';
          canvas.on('mouse:over', function(e) {
            if (e.target) {
              e.target._renderControls(canvas.contextTop, {
                hasControls: false
              })
            }
          });
    
          canvas.on('mouse:out', function(e) {
            canvas.clearContext(canvas.contextTop);
          });
          canvas.on('mouse:down', function(e) {
            canvas.clearContext(canvas.contextTop);
          });
    
          addText = function() {
            var text = new fabric.Textbox("Edit this Text", {
              name: genNextName(),
              left: canvas.getWidth() / canvas.getZoom() / 2,
              top: canvas.getHeight() / canvas.getZoom() / 2,
              width: (canvas.getWidth() / canvas.getZoom()) / 2,
              fill: "#000000",
              originX: "center",
              originY: "center",
              fontFamily: "Inter",
              fontWeight: 400,
              fontSize: 60,
              padding: 20
            });
            text.setControlsVisibility({
              mt: false,
              mb: false,
              ml: true,
              mr: true,
              tl: true,
              tr: true,
              bl: true,
              br: true
            });
    
            canvas.add(text);
            canvas.setActiveObject(text);
            canvas.renderAll();
          };
        })();
    
        function hide_mh_box() {
          canvas.remove(canvas.getItemByAttr("isBB", true));
        }
    
        function show_mh_box() {
          var h = parseInt($("[name=maxHeight]").val());
          var n = new fabric.Rect({
            top: canvas.getActiveObject().get('top'),
            left: canvas.getActiveObject().get('left'),
            width: canvas.getActiveObject().get('width'),
            height: h,
            originX: canvas.getActiveObject().get('originX'),
            originY: canvas.getActiveObject().get('originY'),
            angle: canvas.getActiveObject().get('angle'),
            opacity: 1,
            strokeWidth: 2,
            stroke: "#FF00FF",
            fill: "rgba(0,0,0,0)",
            evented: !1,
            isBB: true
          });
          canvas.add(n);
          canvas.renderAll();
        }
    
        var visited = [];
    
        $(document).ready(function() {
    
          $("input[type=color]").on("input", function() {
            $(this).parent().parent().find('input').first().val(this.value);
          });
    
          $("textarea, input").attr("autocomplete", "off");
    
          if (window !== window.parent) {
            const url = new URL(document.referrer);
            $(".isIframe").removeClass('d-none');
            $(".notIframe").hide();
          }
        });
      </script>
    
      <script>
        function Duplicate(name) {
          Copy(name);
        }
    
        function Copy(name) {
          canvas.getItemByAttr("name", name).clone(function(cloned) {
            cloned.set({
              "name": genNextName()
            })
            _clipboard = cloned;
            Paste(name);
            canvas.renderAll();
          });
        }
    
        function RenameLayer(name) {
          let layerObj = canvas.getItemByAttr("name", name);
    
          let newName = prompt("Rename Layer?", layerObj.name);
    
          while (checkIfNameExists(newName)) {
            alert("Your layer name: " + layerObj.name + " is NOT unique please rename your layer differently.")
            newName = prompt("Rename Layer?", layerObj.name);
          }
    
          setTimeout(() => {
            layerObj.name = newName;
            canvas.renderAll();  
          })
    
        }
    
        function checkIfNameExists(newName) {
          count = 0;
    
          canvas.forEachObject(function(obj, i) {
            if (obj.name == newName) {
              count++;
            }
          });
    
          if (count > 0) {
            return true;
          } else {
            return false;
          }
        }
    
        function Paste(name) {
          _clipboard.clone(function(clonedObj) {
            canvas.discardActiveObject();
            clonedObj.set({
              left: clonedObj.left + 10,
              top: clonedObj.top + 10,
              evented: true,
            });
            if (clonedObj.type === 'activeSelection') {
              clonedObj.canvas = canvas;
              clonedObj.forEachObject(function(obj) {
                canvas.add(obj);
              });
              clonedObj.setCoords();
            } else {
              canvas.add(clonedObj);
            }
            _clipboard.top += 10;
            _clipboard.left += 10;
            canvas.setActiveObject(clonedObj);
            canvas.requestRenderAll();
          });
        }
    
        function genNextName() {
          canvas.renderAll();
          var total = canvas.getObjects().length;
          return String.fromCharCode(65 + total).toLowerCase()
        }
    
        fabric.Object.prototype.transparentCorners = false;
        fabric.Object.prototype.borderColor = '#5cdce4';
        fabric.Object.prototype.cornerColor = 'white';
        fabric.Object.prototype.cornerStrokeColor = 'blue';
        fabric.Object.prototype.cornerStyle = 'circle';
        fabric.Object.prototype.cornerSize = 10;
        fabric.Object.prototype.borderScaleFactor = 2;
      </script>
    </body>
    
    </html>

    This solution is not optimal. But to be honest, the whole code needs serious refactoring. However, perhaps such a solution at the moment will be able to suit you.