Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building Custom Interactive Controls with Fabric.js

Tech May 20 2

Fabric.js allows developers to extend the default corner controls of canvas objects by creating bespoke interaction points. These custom controls can trigger specific operations like removing or duplicating elements, enhancing the usability of graphical editors.

Canvas Initialization and Object Creation

To demonstrate, start by setting up a canvas and adding a rectangular shape.

<canvas id="drawingArea" width="600" height="450"></canvas>

<script>
  const board = new fabric.Canvas('drawingArea')

  const box = new fabric.Rect({
    left: 120,
    top: 60,
    width: 180,
    height: 90,
    fill: '#FFD700',
    stroke: '#90EE90',
    strokeWidth: 3,
    objectCaching: false
  })

  board.add(box)
</script>

Implementing a Removal Control

Defining a custom control typically involves two steps: instantiating the control via fabric.Control and binding an action handler to it. The fabric.Control constructor accepts an object where you specify positioning coordinates (x, y), cursor styles, and rendering logic.

function removeElement(eventData, transformData) {
  const activeItem = transformData.target
  const drawingBoard = activeItem.canvas
  drawingBoard.remove(activeItem)
  drawingBoard.requestRenderAll()
}

box.controls.removeBtn = new fabric.Control({
  x: 0.5,
  y: -0.5,
  offsetY: -20,
  offsetX: 20,
  cursorStyle: 'pointer',
  mouseUpHandler: removeElement,
  render: function(ctx, posX, posY, styleOverride, targetObj) {
    const dimension = this.cornerSize
    ctx.save()
    ctx.fillStyle = '#FF69B4'
    ctx.translate(posX, posY)
    ctx.fillRect(-dimension / 2, -dimension / 2, dimension, dimension)
    ctx.restore()
  },
  cornerSize: 28
})

The render method leverages native Canvas API drawing commands. Using ctx.save() and ctx.restore() ensures the drawing state is preserved and restored properly, preventing style bleed to subsequent render cycles.

Rendering SVG Icons and Adding Duplication

Instead of solid shapes, conttrols can display SVG images encoded as Data URIs. This approach provieds a more polished visual interface. The following implementation integrates both a removal and a duplication control globally on all fabric.Object instances.

<div>
  <button id="spawnBtn">Create Rectangle</button>
</div>
<canvas id="drawingArea" width="600" height="450"></canvas>

<script src="fabric.min.js"></script>
<script>
  const board = new fabric.Canvas('drawingArea')

  const removeSvgUri = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E"

  const duplicateSvgUri = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='iso-8859-1'%3F%3E%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 55.699 55.699' width='100px' height='100px' xml:space='preserve'%3E%3Cpath style='fill:%23010002;' d='M51.51,18.001c-0.006-0.085-0.022-0.167-0.05-0.248c-0.012-0.034-0.02-0.067-0.035-0.1 c-0.049-0.106-0.109-0.206-0.194-0.291v-0.001l0,0c0,0-0.001-0.001-0.001-0.002L34.161,0.293c-0.086-0.087-0.188-0.148-0.295-0.197 c-0.027-0.013-0.057-0.02-0.086-0.03c-0.086-0.029-0.174-0.048-0.265-0.053C33.494,0.011,33.475,0,33.453,0H22.177 c-3.678,0-6.669,2.992-6.669,6.67v1.674h-4.663c-3.678,0-6.67,2.992-6.67,6.67V49.03c0,3.678,2.992,6.669,6.67,6.669h22.677 c3.677,0,6.669-2.991,6.669-6.669v-1.675h4.664c3.678,0,6.669-2.991,6.669-6.669V18.069C51.524,18.045,51.512,18.025,51.51,18.001z M34.454,3.414l13.655,13.655h-8.985c-2.575,0-4.67-2.095-4.67-4.67V3.414z M38.191,49.029c0,2.574-2.095,4.669-4.669,4.669H10.845 c-2.575,0-4.67-2.095-4.67-4.669V15.014c0-2.575,2.095-4.67,4.67-4.67h5.663h4.614v10.399c0,3.678,2.991,6.669,6.668,6.669h10.4 v18.942L38.191,49.029L38.191,49.029z M36.777,25.412h-8.986c-2.574,0-4.668-2.094-4.668-4.669v-8.985L36.777,25.412z M44.855,45.355h-4.664V26.412c0-0.023-0.012-0.044-0.014-0.067c-0.006-0.085-0.021-0.167-0.049-0.249 c-0.012-0.033-0.021-0.066-0.036-0.1c-0.048-0.105-0.109-0.205-0.194-0.29l0,0l0,0c0-0.001-0.001-0.002-0.001-0.002L22.829,8.637 c-0.087-0.086-0.188-0.147-0.295-0.196c-0.029-0.013-0.058-0.021-0.088-0.031c-0.086-0.03-0.172-0.048-0.263-0.053 c-0.021-0.002-0.04-0.013-0.062-0.013h-4.614V6.67c0-2.575,2.095-4.67,4.669-4.67h10.277v10.4c0,3.678,2.992,6.67,6.67,6.67h10.399 v21.616C49.524,43.26,47.429,45.355,44.855,45.355z'/%3E%3C/svg%3E%0A"

  const removeIconElement = document.createElement('img')
  removeIconElement.src = removeSvgUri

  const duplicateIconElement = document.createElement('img')
  duplicateIconElement.src = duplicateSvgUri

  fabric.Object.prototype.transparentCorners = false
  fabric.Object.prototype.cornerColor = 'blue'
  fabric.Object.prototype.cornerStyle = 'circle'

  document.getElementById('spawnBtn').onclick = spawnRect

  function drawControlGraphic(imageElement) {
    return function(ctx, posX, posY, styleOverride, targetObj) {
      const dimension = this.cornerSize
      ctx.save()
      ctx.translate(posX, posY)
      ctx.rotate(fabric.util.degreesToRadians(targetObj.angle))
      ctx.drawImage(imageElement, -dimension / 2, -dimension / 2, dimension, dimension)
      ctx.restore()
    }
  }

  fabric.Object.prototype.controls.removeBtn = new fabric.Control({
    x: 0.5,
    y: -0.5,
    offsetY: -20,
    offsetX: 20,
    cursorStyle: 'pointer',
    mouseUpHandler: removeElement,
    render: drawControlGraphic(removeIconElement),
    cornerSize: 28
  })

  fabric.Object.prototype.controls.duplicateBtn = new fabric.Control({
    x: -0.5,
    y: -0.5,
    offsetY: -20,
    offsetX: -20,
    cursorStyle: 'pointer',
    mouseUpHandler: duplicateElement,
    render: drawControlGraphic(duplicateIconElement),
    cornerSize: 28
  })

  spawnRect()

  function spawnRect() {
    const box = new fabric.Rect({
      left: 120,
      top: 60,
      width: 180,
      height: 90,
      fill: '#FFD700',
      stroke: '#90EE90',
      strokeWidth: 3,
      objectCaching: false
    })

    board.add(box)
    board.setActiveObject(box)
  }

  function removeElement(eventData, transformData) {
    const activeItem = transformData.target
    const drawingBoard = activeItem.canvas
    drawingBoard.remove(activeItem)
    drawingBoard.requestRenderAll()
  }

  function duplicateElement(eventData, transformData) {
    const activeItem = transformData.target
    const drawingBoard = activeItem.canvas
    activeItem.clone(function(clonedItem) {
      clonedItem.left += 15
      clonedItem.top += 15
      drawingBoard.add(clonedItem)
    })
  }
</script>

The drawControlGraphic factory function handles rendering the image onto the canvas context. It adjusts the drawing origin and applies rotation matching the parent object's angle, ensuring the control icon stays visually aligned when the target element is rotated.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.