返回

Canvas 绘图工具集锦:标记、缩放、位移和历史保存

前端

在开发过程中,我花了大量时间来寻找一种可以在 Canvas 上实现图像标记、缩放、位移和历史状态保存的解决方案。网上关于这方面的资料十分稀少,所以我只能自己慢慢地推导出来。

让我们以横坐标为例,首先,我制作了两个相同的元素,并在它们上标记了坐标,然后设置容器属性 overflow:hidden 来隐藏溢出内容。

.container {
  overflow: hidden;
}

接着,我对比了原始大小和放大 3 倍后的结果,发现缩放后的元素的宽度是原始宽度的三倍,而左边隐藏的内容的宽度是原始宽度的两倍。

原始宽度:100px
缩放后宽度:300px
隐藏内容宽度:200px

根据这些信息,我推导出了下面的公式:

隐藏内容宽度 = 缩放后宽度 - 原始宽度

同样的道理,我还推导出以下公式:

隐藏内容高度 = 缩放后高度 - 原始高度

这些公式可以帮助我计算出隐藏内容的宽度和高度,以便在缩放时正确地定位标记。

在实现了缩放功能之后,我又继续实现了标记、位移和历史状态保存功能。整个过程虽然有些复杂,但我最终还是成功了。

现在,我将把这些代码分享给大家。我希望它们能帮助你们在开发中实现类似的功能。

// Canvas 初始化
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// 图片加载
const image = new Image();
image.onload = function() {
  ctx.drawImage(image, 0, 0);
};
image.src = 'image.png';

// 标记工具
const markerTool = {
  isDrawing: false,
  startX: 0,
  startY: 0,
  endX: 0,
  endY: 0,

  startDrawing(e) {
    this.isDrawing = true;
    this.startX = e.clientX;
    this.startY = e.clientY;
  },

  moveDrawing(e) {
    if (!this.isDrawing) {
      return;
    }

    this.endX = e.clientX;
    this.endY = e.clientY;

    ctx.strokeStyle = 'red';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(this.startX, this.startY);
    ctx.lineTo(this.endX, this.endY);
    ctx.stroke();
  },

  endDrawing() {
    this.isDrawing = false;
  }
};

// 缩放工具
const zoomTool = {
  scaleFactor: 1,

  zoomIn() {
    this.scaleFactor *= 2;
    ctx.scale(2, 2);
  },

  zoomOut() {
    this.scaleFactor /= 2;
    ctx.scale(0.5, 0.5);
  }
};

// 位移工具
const panTool = {
  isDragging: false,
  startX: 0,
  startY: 0,

  startDragging(e) {
    this.isDragging = true;
    this.startX = e.clientX;
    this.startY = e.clientY;
  },

  moveDragging(e) {
    if (!this.isDragging) {
      return;
    }

    const dx = e.clientX - this.startX;
    const dy = e.clientY - this.startY;

    ctx.translate(dx, dy);

    this.startX = e.clientX;
    this.startY = e.clientY;
  },

  endDragging() {
    this.isDragging = false;
  }
};

// 历史状态保存
const history = [];

const saveState() {
  history.push(canvas.toDataURL());
}

const restoreState() {
  if (history.length > 0) {
    const dataURL = history.pop();
    const image = new Image();
    image.onload = function() {
      ctx.drawImage(image, 0, 0);
    };
    image.src = dataURL;
  }
}

// 事件监听器
canvas.addEventListener('mousedown', markerTool.startDrawing);
canvas.addEventListener('mousemove', markerTool.moveDrawing);
canvas.addEventListener('mouseup', markerTool.endDrawing);

canvas.addEventListener('mousewheel', zoomTool.zoomIn);
canvas.addEventListener('mousewheel', zoomTool.zoomOut);

canvas.addEventListener('mousedown', panTool.startDragging);
canvas.addEventListener('mousemove', panTool.moveDragging);
canvas.addEventListener('mouseup', panTool.endDragging);

document.getElementById('save-button').addEventListener('click', saveState);
document.getElementById('restore-button').addEventListener('click', restoreState);

我希望这些代码能帮助你们开发出更出色的应用程序。