Skip to main content

如何为 HTML5 画布形状显示上下文菜单?

你想为画布形状显示上下文菜单吗?

要显示上下文菜单,我们需要:

  1. 监听画布容器(舞台)的 contextmenu 事件
  2. 阻止默认的浏览器行为,以便不显示原生的上下文菜单
  3. 使用 Konva 工具或常规 HTML 创建我们自己的上下文菜单

说明:双击舞台以创建一个圆形。尝试在形状上右键单击(上下文菜单)以获取菜单。

import Konva from 'konva';

// 创建一个 div 用作上下文菜单
const menuNode = document.createElement('div');
menuNode.id = 'menu';
menuNode.style.display = 'none';
menuNode.style.position = 'fixed';
menuNode.style.width = '60px';
menuNode.style.backgroundColor = 'white';
menuNode.style.boxShadow = '0 0 5px grey';
menuNode.style.borderRadius = '3px';

// 为菜单创建按钮
const pulseButton = document.createElement('button');
pulseButton.textContent = '脉冲';
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 = '删除';
deleteButton.style.width = '100%';
deleteButton.style.backgroundColor = 'white';
deleteButton.style.border = 'none';
deleteButton.style.margin = '0';
deleteButton.style.padding = '10px';

// 添加悬停效果
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';
});

// 将按钮添加到菜单
menuNode.appendChild(pulseButton);
menuNode.appendChild(deleteButton);
document.body.appendChild(menuNode);

// 设置舞台
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);

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

let currentShape;

// 设置菜单功能
pulseButton.addEventListener('click', () => {
  currentShape.to({
    scaleX: 2,
    scaleY: 2,
    onFinish: () => {
      currentShape.to({ scaleX: 1, scaleY: 1 });
    },
  });
});

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

// 在文档点击时隐藏菜单
window.addEventListener('click', () => {
  menuNode.style.display = 'none';
});

// 添加双击事件以创建新形状
stage.on('dblclick dbltap', function () {
  // 添加一个新形状
  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);
});

// 添加上下文菜单事件
stage.on('contextmenu', function (e) {
  // 防止默认行为
  e.evt.preventDefault();
  if (e.target === stage) {
    // 如果我们在舞台的空白区域,将不执行任何操作
    return;
  }
  currentShape = e.target;
  // 显示菜单
  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';
});