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
- Display a context menu when right-cilcking a canvas element
- Automatically adjust the menu position to avoid overflowing the canvas bounds
- 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 canvasstopContextMenu: Disables the browser's default right-click context menumouse:down: Standard mouse down event, which includes abuttonproperty to identify which mouse button was pressed
When using the mouse:down event:
- Left click:
buttonvalue is1 - Right click:
buttonvalue is3 - Middle click:
buttonvalue is2(requires enablingfireMiddleClick: trueon 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();
}
}