Implementing Right-Click Context Menus for Fabric.js

Fabric.js does not include native right-click event support by default, only standard mouse down, mouse up, and mouse move events. However, right-click context menus are a common UI requirement for interactive canvas applications, so this guide covers a native, framework-agnostic implementation.

Environment Versions

  • Chrome Browser: 96.0.4664.45
  • Fabric.js: 4.6.0

Core Requirements

  1. Display a context menu when right-cilcking a canvas element
  2. Automatically adjust the menu position to avoid overflowing the canvas bounds
  3. Hide the menu when left-clicking empty canvas space

Implementation Approach

The key Fabric.js APIs used in this implementation are:

  • fireRightClick: Enables right-click event detection on the canvas
  • stopContextMenu: Disables the browser's default right-click context menu
  • mouse:down: Standard mouse down event, which includes a button property to identify which mouse button was pressed

When using the mouse:down event:

  • Left click: button value is 1
  • Right click: button value is 3
  • Middle click: button value is 2 (requires enabling fireMiddleClick: true on the canvas)

Full Implementation Code

Styling

/* Relative container for canvas and context menu */
.canvas-container {
  position: relative;
}

/* Fabric canvas with border */
#fabric-canvas {
  border: 1px solid #ccc;
}

/* Hidden context menu by default */
.context-menu {
  visibility: hidden;
  z-index: -100;
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
  background-color: #fff;
  min-width: 120px;
}

/* Individual menu items */
.menu-item {
  box-sizing: border-box;
  padding: 4px 8px;
  border-bottom: 1px solid #ccc;
  cursor: pointer;
  font-family: sans-serif;
  font-size: 14px;
}

/* Hover state for menu items */
.menu-item:hover {
  background-color: antiquewhite;
}

/* Round top corners for first menu item */
.menu-item:first-child {
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
}

/* Remove bottom border and round bottom corners for last menu item */
.menu-item:last-child {
  border-bottom: none;
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: 4px;
}

HTML Structure

<div class="canvas-container">
  <canvas id="fabric-canvas" width="600" height="600"></canvas>
  <div id="context-menu" class="context-menu">
    <div class="menu-item">Duplicate</div>
    <div class="menu-item">Bring to Front</div>
    <div class="menu-item">Send to Back</div>
    <div class="menu-item" onclick="deleteSelected()">Delete</div>
  </div>
</div>

JavaScript Logic

let fabricCanvas;
let selectedTarget = null;
const contextMenu = document.getElementById('context-menu');

window.onload = function() {
  console.log(`Fabric.js version: ${fabric.version}`);
  initializeCanvas();
  // Prevent default right-click on menu itself
  contextMenu.oncontextmenu = (e) => e.preventDefault();
};

function initializeCanvas() {
  // Initialize canvas with right-click support
  fabricCanvas = new fabric.Canvas('fabric-canvas', {
    fireRightClick: true,
    stopContextMenu: true
  });

  // Create sample canvas elements
  const orangeRect = new fabric.Rect({
    left: 10,
    top: 510,
    fill: 'orange',
    width: 40,
    height: 40
  });

  const roundedPinkRect = new fabric.Rect({
    left: 510,
    top: 10,
    fill: 'pink',
    width: 40,
    height: 40,
    rx: 10,
    ry: 10
  });

  const greenCircle = new fabric.Circle({
    radius: 30,
    fill: 'green',
    left: 20,
    top: 20
  });

  const blueTriangle = new fabric.Triangle({
    width: 80,
    height: 100,
    fill: 'blue',
    left: 500,
    top: 480
  });

  fabricCanvas.add(orangeRect, roundedPinkRect, greenCircle, blueTriangle);

  // Attach mouse down event listener
  fabricCanvas.on('mouse:down', handleCanvasMouseDown);
}

function handleCanvasMouseDown(event) {
  // Hide menu for non-right-click events or clicks on empty canvas
  if (event.button !== 3 || !event.target) {
    hideContextMenu();
    return;
  }

  // Store the selected canvas element
  selectedTarget = event.target;

  // Calculate menu position
  const menuWidth = contextMenu.offsetWidth;
  const menuHeight = contextMenu.offsetHeight;
  let menuX = event.pointer.x;
  let menuY = event.pointer.y;

  // Adjust position to avoid overflowing right canvas edge
  if (fabricCanvas.width - menuX <= menuWidth) {
    menuX -= menuWidth;
  }

  // Adjust position to avoid overflowing bottom canvas edge
  if (fabricCanvas.height - menuY <= menuHeight) {
    menuY -= menuHeight;
  }

  // Show and position the context menu
  contextMenu.style.visibility = 'visible';
  contextMenu.style.left = `${menuX}px`;
  contextMenu.style.top = `${menuY}px`;
  contextMenu.style.zIndex = '100';
}

function hideContextMenu() {
  contextMenu.style.visibility = 'hidden';
  contextMenu.style.left = '0';
  contextMenu.style.top = '0';
  contextMenu.style.zIndex = '-100';
  selectedTarget = null;
}

function deleteSelected() {
  if (selectedTarget) {
    fabricCanvas.remove(selectedTarget);
    hideContextMenu();
  }
}

Tags: fabric.js HTML5 Canvas javascript Context Menu

Posted on Tue, 09 Jun 2026 18:05:29 +0000 by raytri