GuerillaRadio
GuerillaRadio

Reputation: 1297

FabricJS - Issues with origin when drawing and transforming path

This is a really strange issue that I can't get my head around and relates this this issue I previously posted.

Users can draw lines (paths) on a canvas. Once the line is drawn there are 3 anchor points that are positioned on the line. A user should be able to either drag the entire line to reposition it or drag either of the anchors to change either the start point, end point or the quadratic curve.

The issue I have is that the positioning of the path itself when initially drawn is off, whilst the positioning of the anchors is correct. If you then drag that path around you can see that everything stays in the same position.

Please note that the reason I am redrawing the path each time it is altered is due to the bounding box around the line not updating if you drag either of the anchors (example of issue here). I also need to ensure that all values once the canvas is saved are up to date so I can redraw it at a later date.

I'm almost certain it is something to do with the originX and originY of the line. Changing this does have an effect (if you comment out originX: 'center' and originY: 'center' from the Line variable you can see that the initial draw is correct (albeit drawn in a strange way that I don't like) but any subsequent moving or repositioning of anchors causes jumps around the canvas).

Apologies for the lengthy code snippet.

let canvas;
let line;
let lineAnchorStart;
let lineAnchorEnd;
let lineAnchorBend;
let activeItem;
let drawingModeOn = true;
const button = document.getElementById('toggle-drawing-mode');

const Line = fabric.util.createClass(fabric.Path, {
  type: 'line',

  initialize(element, options) {
    options || (options = {});
    this.callSuper('initialize', element, options);

    // Set default options
    this.set({
      objectCaching: false,
      hasControls: false,
      // Comment out the below 2 lines
      originX: 'center',
      originY: 'center',
      fill: 'transparent',
      strokeWidth: 2,
      stroke: 'black',
      customProps: {
        category: 'lines',
      },
    });
  },
})

// Repositioning the line anchors after the line is moved or selected line is changed
const repositionLineAnchors = (line) => {
  lineAnchorStart.set({
    left: line.path[0][1],
    top: line.path[0][2]
  }).setCoords();

  lineAnchorEnd.set({
    left: line.path[1][3],
    top: line.path[1][4]
  }).setCoords();

  // If the line is perfectly straight then we want to keep the bend anchor in the middle
  // But if it has had bend applied to it then we let it stay where it was dragged
  if ((line.path[1][1] === line.path[1][3]) && (line.path[1][2] === line.path[1][4])) {
    const centerX = (line.path[0][1] + line.path[1][3]) / 2;
    const centerY = (line.path[0][2] + line.path[1][4]) / 2;
    lineAnchorBend.set({
      left: centerX,
      top: centerY
    }).setCoords();
  } else {
    lineAnchorBend.set({
      left: line.path[1][1],
      top: line.path[1][2]
    }).setCoords();
  }
}

// If the line anchors themselves are moved the
const handleLineAnchorMove = (target) => {
  switch (target.customProps.category) {
    // Moving the line anchors
    case 'line_anchor':
      switch (target.customProps.type) {
        case 'line_anchor_start':
          activeItem.path[0][1] = target.left;
          activeItem.path[0][2] = target.top;
          activeItem.setCoords();
          break;

        case 'line_anchor_end':
          // If the line is perfectly straight then we want to keep the quadratic value the same as the end point to avoid bending it
          // But if it has had bend applied to it then the two can be treated separately
          if ((activeItem.path[1][1] === activeItem.path[1][3]) && (activeItem.path[1][2] === activeItem.path[1][4])) {
            activeItem.path[1][1] = target.left;
            activeItem.path[1][2] = target.top;
          }
          activeItem.path[1][3] = target.left;
          activeItem.path[1][4] = target.top;
          activeItem.setCoords();
          break;

        case 'line_anchor_bend':
          activeItem.path[1][1] = target.left;
          activeItem.path[1][2] = target.top;
          activeItem.setCoords();
          break;
          // no default
      }
      // no default
  }
  fabricCanvas.renderAll();
}

const transformedPoint = (target) => {
  const points = [];
  const path = target.path;
  points.push(new fabric.Point(path[0][1], path[0][2]));
  points.push(new fabric.Point(path[1][3], path[1][4]));
  points.push(new fabric.Point(path[1][1], path[1][2]));
  const matrix = target.calcTransformMatrix();
  return points
    .map(p => new fabric.Point(p.x - target.minX, p.y - target.minY))
    .map(p => fabric.util.transformPoint(p, matrix));
}

const redrawPath = (oldLine) => {
  const transformedPoints = transformedPoint(oldLine);
  const path = [
    [],
    []
  ];
  path[0][0] = 'M';
  path[0][1] = transformedPoints[0].x;
  path[0][2] = transformedPoints[0].y;
  path[1][0] = 'Q';
  path[1][1] = transformedPoints[2].x;
  path[1][2] = transformedPoints[2].y;
  path[1][3] = transformedPoints[1].x;
  path[1][4] = transformedPoints[1].y;

  const newLine = drawLine(path);
  repositionLineAnchors(newLine);
  fabricCanvas.remove(oldLine).add(newLine).setActiveObject(newLine).renderAll();
}

const addLine = () => {
  let isDown;
  let startPoint;

  fabricCanvas.on('mouse:down', (options) => {
    hideLineAnchors();
    isDown = true;
    startPoint = fabricCanvas.getPointer(options.e);
    const path = [
      [],
      []
    ];
    path[0][0] = 'M';
    path[0][1] = startPoint.x;
    path[0][2] = startPoint.y;
    path[1][0] = 'Q';
    path[1][1] = startPoint.x;
    path[1][2] = startPoint.y;
    path[1][3] = startPoint.x;
    path[1][4] = startPoint.y;
    line = drawLine(path);
    line.selectable = false; // This is needed to prevent newly added lines from being dragged if drawing a line right next to them
    fabricCanvas.add(line).renderAll();
  });

  fabricCanvas.on('mouse:move', (options) => {
    if (!isDown) return;
    const pointer = fabricCanvas.getPointer(options.e);
    const lineWidth = pointer.x - startPoint.x;
    const lineHeight = pointer.y - startPoint.y;
    line.path[1][1] = pointer.x;
    line.path[1][2] = pointer.y;
    line.path[1][3] = pointer.x;
    line.path[1][4] = pointer.y;
    line.set({
      height: Math.abs(lineHeight),
      width: Math.abs(lineWidth)
    }).setCoords();
    lineAnchorEnd.set({
      left: pointer.x,
      top: pointer.y
    });
    fabricCanvas.renderAll();
  });

  fabricCanvas.on('mouse:up', (options) => {
    isDown = false;
    const endPoint = fabricCanvas.getPointer(options.e);
    redrawPath(line);
    disableDrawingMode();
  });
}

const handleObjectSelected = (e) => {
  let selectedItem = e.target;
  switch (selectedItem.customProps.category) {
    case 'line_anchor':
      // If we select a line anchor we actually want the line to be the active object
      selectedItem = activeItem;
      disableDrawingMode();
      break;

    case 'lines':
      repositionLineAnchors(selectedItem);
      showLineAnchors();
      fabricCanvas
        .bringToFront(lineAnchorStart)
        .bringToFront(lineAnchorEnd)
        .bringToFront(lineAnchorBend)
        .renderAll();
      break;
  }

  activeItem = selectedItem;
}

const handleObjectMoving = (e) => {
  const selectedItem = e.target;
  // If not a group
  if (selectedItem.customProps) {
    switch (selectedItem.customProps.category) {
      case 'line_anchor':
        switch (selectedItem.customProps.type) {
          case 'line_anchor_start':
          case 'line_anchor_end':
            lineAnchorBend.visible = false;
            // no default
        }
        handleLineAnchorMove(selectedItem);
        break;
      case 'lines':
        {
          lineAnchorStart.visible === true && hideLineAnchors();
          break;
        }
        // no default
    }
  }
}

const handleObjectModified = (e) => {
  const selectedItem = e.target;
  // If not a group
  if (selectedItem.customProps) {
    switch (selectedItem.customProps.category) {
      case 'lines':
        redrawPath(selectedItem);
        showLineAnchors();
        break;

      case 'line_anchor':
        redrawPath(activeItem);
        showLineAnchors();
        break;
        // no default
    }
  }
}

const disableDrawingMode = () => {
	drawingModeOn = false;
  setButtonText();
  fabricCanvas.selection = true;
  fabricCanvas.forEachObject((object, i) => {
    // This is to prevent the pitch background from being set to selectable (it is 0 in the object array)
    if (i > 0) {
      object.selectable = true;
    }
  });
  fabricCanvas.defaultCursor = 'default';
  fabricCanvas.hoverCursor = 'move';
  // Remove event listeners
  fabricCanvas
    .off('mouse:down')
    .off('mouse:move')
    .off('mouse:up')
    .off('mouse:out');
}

const enableDrawingMode = () => {
	drawingModeOn = true;
  setButtonText();
  fabricCanvas.selection = false;
  fabricCanvas.forEachObject((object) => {
    object.selectable = false;
  });
  // Allow line anchors to be draggable while in drawing mode
  lineAnchorStart.selectable = true;
  lineAnchorEnd.selectable = true;
  lineAnchorBend.selectable = true;
  fabricCanvas.defaultCursor = 'crosshair';
  fabricCanvas.hoverCursor = 'crosshair';
  lineAnchorStart.hoverCursor = 'move';
  lineAnchorEnd.hoverCursor = 'move';
  lineAnchorBend.hoverCursor = 'move';
  addLine();
}

const addLineAnchors = () => {
  lineAnchorStart = createLineAnchor('line_anchor_start');
  lineAnchorEnd = createLineAnchor('line_anchor_end');
  lineAnchorBend = createLineAnchorBend('line_anchor_bend');
  fabricCanvas.add(lineAnchorStart, lineAnchorEnd, lineAnchorBend);
}

const showLineAnchors = () => {
  if (lineAnchorStart) {
    lineAnchorStart.visible = true;
    lineAnchorEnd.visible = true;
    lineAnchorBend.visible = true;
  }
}

const hideLineAnchors = () => {
  if (lineAnchorStart) {
    lineAnchorStart.visible = false;
    lineAnchorEnd.visible = false;
    lineAnchorBend.visible = false;
  }
}

const createLineAnchor = anchorType => (
  new fabric.Rect({
    left: 0,
    top: 0,
    hasBorders: false,
    hasControls: false,
    originX: 'center',
    originY: 'center',
    height: 20,
    width: 20,
    strokeWidth: 2,
    stroke: 'green',
    fill: 'rgba(255, 255, 255, 0.1)',
    visible: false,
    excludeFromExport: true,
    customProps: {
      category: 'line_anchor',
      type: anchorType,
    },
  })
)

const createLineAnchorBend = anchorType => (
  new fabric.Circle({
    left: 0,
    top: 0,
    hasBorders: false,
    hasControls: false,
    originX: 'center',
    originY: 'center',
    radius: 10,
    strokeWidth: 2,
    stroke: 'blue',
    fill: 'rgba(63, 149, 220, 0.5)',
    visible: false,
    excludeFromExport: true,
    customProps: {
      category: 'line_anchor',
      type: anchorType,
    },
  })
)

const setButtonText = () => {
  if (drawingModeOn === true) {
    button.textContent = 'Disable Drawing Mode';
  } else {
    button.textContent = 'Enable Drawing Mode';
  }
}

const setDrawingMode = () => {
  if (drawingModeOn === true) {
    enableDrawingMode();
  } else {
    disableDrawingMode();
  }
}

const initFabric = () => {
  fabricCanvas = new fabric.Canvas('c', {
    height: 1000,
    width: 1000,
    targetFindTolerance: 15,
    selection: false,
    preserveObjectStacking: true,
    perPixelTargetFind: true, // To prevent the line having a selectable rectangle drawn around it and instead only have it selectable on direct click
  });

  fabricCanvas.on({
    'object:selected': handleObjectSelected,
    'object:moving': handleObjectMoving,
    'object:modified': handleObjectModified,
  });

  addLineAnchors();
}

const drawLine = (points) => (
  new Line(points)
)

button.addEventListener('click', () => {
  drawingModeOn = !drawingModeOn;
  setDrawingMode();
  setButtonText();
});

initFabric();
setDrawingMode();
canvas {
  border: 1px solid tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.21/fabric.min.js"></script>
<canvas id="c"></canvas>
<button id="toggle-drawing-mode"></button>

Upvotes: 1

Views: 1960

Answers (1)

Observer
Observer

Reputation: 3706

It's hard to debug all code, but on 'mouse-up' event top and left positions are changed compering to the oldLine. Before drawing newLine I did this change inside **redrawPath** function:

newLine.set({"top": newLine.pathOffset.y, "left": newLine.pathOffset.x});
repositionLineAnchors(newLine);

Also, inside transformedPoint I removed logic to map points:

return points;

instead of:

return points
    .map(p => new fabric.Point(p.x - target.minX, p.y - target.minY))
    .map(p => fabric.util.transformPoint(p, matrix));

Here is a fiddle

UPDATE:

Your logic was almost good, you need just set

  1. originX: "left" and originY: "top" instead of 'center'
  2. Subtract target.width, target.height and target.strokeWidth from the map - const halfStroke = target.get("strokeWidth") / 2; return points.map(p => new fabric.Point(p.x - target.minX - target.width / 2 - halfStroke , p.y - target.minY - target.height / 2 - halfStroke)).map(p => fabric.util.transformPoint(p, matrix));.
  3. And on mouse-move event set pathOffset, width and height like:
const dims = line._parseDimensions();
line.setWidth(dims.width);
line.setHeight(dims.height);
line.pathOffset.x = line.width/2 + line.left;
line.pathOffset.y = line.height/2 + line.top;

    let canvas;
    let line;
    let lineAnchorStart;
    let lineAnchorEnd;
    let lineAnchorBend;
    let activeItem;
    let drawingModeOn = true;
    let fabricCanvas = null;
    const button = document.getElementById('toggle-drawing-mode');

    const Line = fabric.util.createClass(fabric.Path, {
      type: 'line',

      initialize(element, options) {
        options || (options = {});
        this.callSuper('initialize', element, options);

        // Set default options
        this.set({
          objectCaching: false,
          hasControls: false,
          // Comment out the below 2 lines
          originX: 'left',
          originY: 'top',
          fill: 'transparent',
          strokeWidth: 2,
          stroke: 'black',
          customProps: {
            category: 'lines',
          },
        });
      },
    })

    // Repositioning the line anchors after the line is moved or selected line is changed
    const repositionLineAnchors = (line) => {
      lineAnchorStart.set({
        left: line.path[0][1],
        top: line.path[0][2]
      }).setCoords();

      lineAnchorEnd.set({
        left: line.path[1][3],
        top: line.path[1][4]
      }).setCoords();

      // If the line is perfectly straight then we want to keep the bend anchor in the middle
      // But if it has had bend applied to it then we let it stay where it was dragged
      if ((line.path[1][1] === line.path[1][3]) && (line.path[1][2] === line.path[1][4])) {
        const centerX = (line.path[0][1] + line.path[1][3]) / 2;
        const centerY = (line.path[0][2] + line.path[1][4]) / 2;
        lineAnchorBend.set({
          left: centerX,
          top: centerY
        }).setCoords();
      } else {
        lineAnchorBend.set({
          left: line.path[1][1],
          top: line.path[1][2]
        }).setCoords();
      }
    }

    // If the line anchors themselves are moved the
    const handleLineAnchorMove = (target) => {
      switch (target.customProps.category) {
        // Moving the line anchors
        case 'line_anchor':
          switch (target.customProps.type) {
            case 'line_anchor_start':
              activeItem.path[0][1] = target.left;
              activeItem.path[0][2] = target.top;
              activeItem.setCoords();
              break;

            case 'line_anchor_end':
              // If the line is perfectly straight then we want to keep the quadratic value the same as the end point to avoid bending it
              // But if it has had bend applied to it then the two can be treated separately
              if ((activeItem.path[1][1] === activeItem.path[1][3]) && (activeItem.path[1][2] === activeItem.path[1][4])) {
                activeItem.path[1][1] = target.left;
                activeItem.path[1][2] = target.top;
              }
              activeItem.path[1][3] = target.left;
              activeItem.path[1][4] = target.top;
              activeItem.setCoords();
              break;

            case 'line_anchor_bend':
              activeItem.path[1][1] = target.left;
              activeItem.path[1][2] = target.top;
              activeItem.setCoords();
              break;
              // no default
          }
          // no default
      }
      fabricCanvas.renderAll();
    }

    const transformedPoint = (target) => {
      const points = [];
      const path = target.path;
      points.push(new fabric.Point(path[0][1], path[0][2]));
      points.push(new fabric.Point(path[1][3], path[1][4]));
      points.push(new fabric.Point(path[1][1], path[1][2]));
      const matrix = target.calcTransformMatrix();
      const halfStroke = target.get("strokeWidth") / 2;
      return points
         .map(p => new fabric.Point(p.x - target.minX - target.width / 2 - halfStroke, p.y - target.minY - target.height / 2 - halfStroke))
         .map(p => fabric.util.transformPoint(p, matrix));
    }

    const redrawPath = (oldLine) => {
    //oldLine.set({"originX": "left", "originY": "top"});
      const transformedPoints = transformedPoint(oldLine);
      const path = [
        [],
        []
      ];
      path[0][0] = 'M';
      path[0][1] = transformedPoints[0].x;
      path[0][2] = transformedPoints[0].y;
      path[1][0] = 'Q';
      path[1][1] = transformedPoints[2].x;
      path[1][2] = transformedPoints[2].y;
      path[1][3] = transformedPoints[1].x;
      path[1][4] = transformedPoints[1].y;
      



      const newLine = drawLine(path);
        repositionLineAnchors(newLine);
      fabricCanvas.remove(oldLine).add(newLine).setActiveObject(newLine).renderAll();
    }

    const addLine = () => {
      let isDown;
      let startPoint;

      fabricCanvas.on('mouse:down', (options) => {
        hideLineAnchors();
        isDown = true;
        startPoint = fabricCanvas.getPointer(options.e);
        const path = [
          [],
          []
        ];
        path[0][0] = 'M';
        path[0][1] = startPoint.x;
        path[0][2] = startPoint.y;
        path[1][0] = 'Q';
        path[1][1] = startPoint.x;
        path[1][2] = startPoint.y;
        path[1][3] = startPoint.x;
        path[1][4] = startPoint.y;
        line = drawLine(path);
        line.selectable = false; // This is needed to prevent newly added lines from being dragged if drawing a line right next to them
        fabricCanvas.add(line).renderAll();
      });

      fabricCanvas.on('mouse:move', (options) => {
        if (!isDown) return;
        const pointer = fabricCanvas.getPointer(options.e);
        line.path[1][1] = pointer.x;
        line.path[1][2] = pointer.y;
        line.path[1][3] = pointer.x;
        line.path[1][4] = pointer.y;
        const dims = line._parseDimensions();
        line.setWidth(dims.width);
        line.setHeight(dims.height);
        line.pathOffset.x = line.width/2 + line.left;
            line.pathOffset.y = line.height/2 + line.top;
       
        lineAnchorEnd.set({
          left: pointer.x,
          top: pointer.y
        });
         line.setCoords();
        fabricCanvas.renderAll();
      });

      fabricCanvas.on('mouse:up', (options) => {
        isDown = false;
        const endPoint = fabricCanvas.getPointer(options.e);
        redrawPath(line);
        disableDrawingMode();
      });
    }

    const handleObjectSelected = (e) => {
      let selectedItem = e.target;
      switch (selectedItem.customProps.category) {
        case 'line_anchor':
          // If we select a line anchor we actually want the line to be the active object
          selectedItem = activeItem;
          disableDrawingMode();
          break;

        case 'lines':
          repositionLineAnchors(selectedItem);
          showLineAnchors();
          fabricCanvas
            .bringToFront(lineAnchorStart)
            .bringToFront(lineAnchorEnd)
            .bringToFront(lineAnchorBend)
            .renderAll();
          break;
      }

      activeItem = selectedItem;
    }

    const handleObjectMoving = (e) => {
      const selectedItem = e.target;
      // If not a group
      if (selectedItem.customProps) {
        switch (selectedItem.customProps.category) {
          case 'line_anchor':
            switch (selectedItem.customProps.type) {
              case 'line_anchor_start':
              case 'line_anchor_end':
                lineAnchorBend.visible = false;
                // no default
            }
            handleLineAnchorMove(selectedItem);
            break;
          case 'lines':
            {
              lineAnchorStart.visible === true && hideLineAnchors();
              break;
            }
            // no default
        }
      }
    }

    const handleObjectModified = (e) => {
      const selectedItem = e.target;
      // If not a group
      if (selectedItem.customProps) {
        switch (selectedItem.customProps.category) {
          case 'lines':
            redrawPath(selectedItem);
            showLineAnchors();
            break;

          case 'line_anchor':
            redrawPath(activeItem);
            showLineAnchors();
            break;
            // no default
        }
      }
    }

    const disableDrawingMode = () => {
        drawingModeOn = false;
      setButtonText();
      fabricCanvas.selection = true;
      fabricCanvas.forEachObject((object, i) => {
        // This is to prevent the pitch background from being set to selectable (it is 0 in the object array)
        if (i > 0) {
          object.selectable = true;
        }
      });
      fabricCanvas.defaultCursor = 'default';
      fabricCanvas.hoverCursor = 'move';
      // Remove event listeners
      fabricCanvas
        .off('mouse:down')
        .off('mouse:move')
        .off('mouse:up')
        .off('mouse:out');
    }

    const enableDrawingMode = () => {
        drawingModeOn = true;
      setButtonText();
      fabricCanvas.selection = false;
      fabricCanvas.forEachObject((object) => {
        object.selectable = false;
      });
      // Allow line anchors to be draggable while in drawing mode
      lineAnchorStart.selectable = true;
      lineAnchorEnd.selectable = true;
      lineAnchorBend.selectable = true;
      fabricCanvas.defaultCursor = 'crosshair';
      fabricCanvas.hoverCursor = 'crosshair';
      lineAnchorStart.hoverCursor = 'move';
      lineAnchorEnd.hoverCursor = 'move';
      lineAnchorBend.hoverCursor = 'move';
      addLine();
    }

    const addLineAnchors = () => {
      lineAnchorStart = createLineAnchor('line_anchor_start');
      lineAnchorEnd = createLineAnchor('line_anchor_end');
      lineAnchorBend = createLineAnchorBend('line_anchor_bend');
      fabricCanvas.add(lineAnchorStart, lineAnchorEnd, lineAnchorBend);
    }

    const showLineAnchors = () => {
      if (lineAnchorStart) {
        lineAnchorStart.visible = true;
        lineAnchorEnd.visible = true;
        lineAnchorBend.visible = true;
      }
    }

    const hideLineAnchors = () => {
      if (lineAnchorStart) {
        lineAnchorStart.visible = false;
        lineAnchorEnd.visible = false;
        lineAnchorBend.visible = false;
      }
    }

    const createLineAnchor = anchorType => (
      new fabric.Rect({
        left: 0,
        top: 0,
        hasBorders: false,
        hasControls: false,
        originX: 'center',
        originY: 'center',
        height: 20,
        width: 20,
        strokeWidth: 2,
        stroke: 'green',
        fill: 'rgba(255, 255, 255, 0.1)',
        visible: false,
        excludeFromExport: true,
        customProps: {
          category: 'line_anchor',
          type: anchorType,
        },
      })
    )

    const createLineAnchorBend = anchorType => (
      new fabric.Circle({
        left: 0,
        top: 0,
        hasBorders: false,
        hasControls: false,
        originX: 'center',
        originY: 'center',
        radius: 10,
        strokeWidth: 2,
        stroke: 'blue',
        fill: 'rgba(63, 149, 220, 0.5)',
        visible: false,
        excludeFromExport: true,
        customProps: {
          category: 'line_anchor',
          type: anchorType,
        },
      })
    )

    const setButtonText = () => {
      if (drawingModeOn === true) {
        button.textContent = 'Disable Drawing Mode';
      } else {
        button.textContent = 'Enable Drawing Mode';
      }
    }

    const setDrawingMode = () => {
      if (drawingModeOn === true) {
        enableDrawingMode();
      } else {
        disableDrawingMode();
      }
    }

    const initFabric = () => {
      fabricCanvas = new fabric.Canvas('c', {
        height: 1000,
        width: 1000,
        targetFindTolerance: 15,
        selection: false,
        preserveObjectStacking: true,
        perPixelTargetFind: true, // To prevent the line having a selectable rectangle drawn around it and instead only have it selectable on direct click
      });

      fabricCanvas.on({
        'object:selected': handleObjectSelected,
        'object:moving': handleObjectMoving,
        'object:modified': handleObjectModified,
      });

      addLineAnchors();
    }

    const drawLine = (points) => (
      new Line(points)
    )

    button.addEventListener('click', () => {
      drawingModeOn = !drawingModeOn;
      setDrawingMode();
      setButtonText();
    });

    initFabric();
    setDrawingMode();
 canvas {
      border: 1px solid tomato;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
      <canvas id="c"></canvas>
      <button id="toggle-drawing-mode"></button>

Upvotes: 2

Related Questions