Skip to main content

How to show a context menu for HTML5 canvas shape?

Do you want to show a context menu for a canvas shape?

To show a context menu we have to:

  1. Listen to contextmenu event on canvas container (stage)
  2. Prevent default browser behavior, so we don't see native context menu
  3. Create our own context menu with Konva tools or regular html

Instructions: double click on the stage to create a circle. Try right click (context menu) on shapes for a menu.

import Konva from 'konva';

// Create a div to use as a context menu
const menuNode = document.createElement('div');
menuNode.id = 'menu';
menuNode.style.display = 'none';
menuNode.style.position = 'absolute';
menuNode.style.width = '60px';
menuNode.style.backgroundColor = 'white';
menuNode.style.boxShadow = '0 0 5px grey';
menuNode.style.borderRadius = '3px';

// Create buttons for the menu
const pulseButton = document.createElement('button');
pulseButton.textContent = 'Pulse';
pulseButton.style.width = '100%';
pulseButton.style.backgroundColor = 'white';
pulseButton.style.border = 'none';
pulseButton.style.margin = '0';
pulseButton.style.padding = '10px';

const deleteButton = document.createElement('button');
deleteButton.textContent = 'Delete';
deleteButton.style.width = '100%';
deleteButton.style.backgroundColor = 'white';
deleteButton.style.border = 'none';
deleteButton.style.margin = '0';
deleteButton.style.padding = '10px';

// Add hover effects
pulseButton.addEventListener('mouseover', () => {
  pulseButton.style.backgroundColor = 'lightgray';
});
pulseButton.addEventListener('mouseout', () => {
  pulseButton.style.backgroundColor = 'white';
});

deleteButton.addEventListener('mouseover', () => {
  deleteButton.style.backgroundColor = 'lightgray';
});
deleteButton.addEventListener('mouseout', () => {
  deleteButton.style.backgroundColor = 'white';
});

// Add buttons to menu
menuNode.appendChild(pulseButton);
menuNode.appendChild(deleteButton);
document.body.appendChild(menuNode);

// Set up the stage
const width = window.innerWidth;
const height = window.innerHeight;

const stage = new Konva.Stage({
  container: 'container',
  width: width,
  height: height,
});

const layer = new Konva.Layer();
stage.add(layer);

// add default shape
const shape = new Konva.Circle({
  x: stage.width() / 2,
  y: stage.height() / 2,
  radius: 50,
  fill: 'red',
  shadowBlur: 10,
});
layer.add(shape);

let currentShape;

// Setup the menu functionality
pulseButton.addEventListener('click', () => {
  currentShape.to({
    scaleX: 2,
    scaleY: 2,
    onFinish: () => {
      currentShape.to({ scaleX: 1, scaleY: 1 });
    },
  });
});

deleteButton.addEventListener('click', () => {
  currentShape.destroy();
});

// Hide menu on document click
window.addEventListener('click', () => {
  menuNode.style.display = 'none';
});

// Add double click event to create new shapes
stage.on('dblclick dbltap', function () {
  // add a new shape
  const newShape = new Konva.Circle({
    x: stage.getPointerPosition().x,
    y: stage.getPointerPosition().y,
    radius: 10 + Math.random() * 30,
    fill: Konva.Util.getRandomColor(),
    shadowBlur: 10,
  });
  layer.add(newShape);
});

// Add context menu event
stage.on('contextmenu', function (e) {
  // prevent default behavior
  e.evt.preventDefault();
  if (e.target === stage) {
    // if we are on empty place of the stage we will do nothing
    return;
  }
  currentShape = e.target;
  // show menu
  menuNode.style.display = 'initial';
  const containerRect = stage.container().getBoundingClientRect();
  menuNode.style.top =
    containerRect.top + stage.getPointerPosition().y + 4 + 'px';
  menuNode.style.left =
    containerRect.left + stage.getPointerPosition().x + 4 + 'px';
});