Canvas实现画布的缩放

发布时间 2023-03-23 13:55:36作者: 火星写程序

主要介绍三种方式:

首先创建一个index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>iot</title>
</head>
<body>
  <canvas id="canvas" width="800" height="800" style="width: 800px;height: 800px;border: 1px solid gray;margin: auto;display: flex;"></canvas>
</body>
</html>
<script type="module">
  import index from './index.js';
  window.onload = (() => {
    index.init();
  })
</script>
index.html

一、使用canvas style属性和 width以及height属性实现缩放效果

优点:代码简单、易开发

缺点:当放大活缩小到一定倍数时容易失真,且只能围绕画布左上角缩放,效果不好,且不能实现编辑等功能

原理:canvas中style样式的height和width属性修饰的是DOM元素的样式,而canvas自有的height和width属性描述的是画布的大小,当这两种样式等比例关系时,就可以实现放大和缩小

/**
 * 通过改变canvas的样式来实现缩放,原理是通过canvas的style中height和width 以及自带的width和height属性不同的比例实现缩放
 * flag true:放大 false: 缩小
 */
function zoom1(flag = true) {
  const curH = Number(canvas.getAttribute('height'));
  const curW = Number(canvas.getAttribute('width'));
  canvas.setAttribute('width', flag ? curW * scale : curW / scale);
  canvas.setAttribute('height', flag ? curH * scale : curH / scale);
}

 

 

 

二、使用canvas API scale方法实现图形的缩放

优点:代码简洁,可以按照鼠标所在位置进行缩放

缺点:图形边框等会随着缩放,且有一定的失真;缩放后,图形原始坐标点会随着缩放等比例变化,不方便实现编辑功能

/**
 * 通过改变canvas的样式来实现缩放,原理是通过canvas的scale属性缩放横纵轴
 * flag true:放大 false: 缩小
 */
function zoom2(flag = true) {
  const beta = flag ? 1 / scale : scale;
  ctx.scale(beta, beta);
}

 

 

三、根据缩放比例重新计算点坐标,首推这种方式

优点:图形边框等不会随着缩放而变化,图形不会失真,可以根据缩放后的坐标点实现一些编辑功能(比如选中高亮)

缺点:需要针对不同的图形元素动态计算坐标点,逻辑较为繁琐

已缩小为例,原理图如下:

缩小时需要同时考虑图形距离鼠标位置更近以及图形大小也同比例缩小,所以最好办法是:

1. 计算缩小后图形中心点距离鼠标的位置B,可以根据缩小前图形中心点A的横纵坐标乘以一个比例得到。

2. 再根据位置B计算各个顶点的坐标,计算时要注意各个顶点之间的距离也要等比例缩小

/**
 * 通过改变坐标点信息实现缩放,可以设置缩放的中心点
 * flag true:放大 false: 缩小
 */
function zoom3(flag = true, event) {
  // 记录当前鼠标的相对位置
  initX = event.offsetX;
  initY = event.offsetY;
  // 计算放大缩小的比例
  multi = 1 * (flag ? 1 / scale : scale);

  // 获取图形中心点
  const center = getCenter();
  // 获取图形最大最小的顶点坐标
  const maxMin = getMaxMin();
  // 计算缩放后的中心点坐标
  const newC = [center[0] - (initX - center[0]) * (multi - 1), center[0] - (initY - center[1]) * (multi - 1)];

  // 根据缩放后的中心点计算各个顶点信息
  coordinates = coordinates.map(e => {
    const operateX = center[0] - e[0] > 0 ? 1 : -1;
    const operateY = center[1] - e[1] > 0 ? 1 : -1;
    return [newC[0] - (maxMin.maxX - maxMin.minX) * multi / 2 * operateX, newC[1] - (maxMin.maxY - maxMin.minY) * multi / 2 * operateY];
  })
}

 

下面附上完整的JS代码

let canvas;
let ctx;
let coordinates = [[50, 50], [250, 50], [250, 250], [50, 250]];
const scale = 0.8;
let [initX, initY] = [0, 0];

let multi = 1;
function init() {
  canvas = document.getElementById('canvas');
  ctx = canvas.getContext('2d');
  draw();
  drawText();
  addEvent();
}

function draw () {
  ctx.lineWidth = 4;
  ctx.strokeStyle = 'rgba(153, 153, 153, 1)';
  ctx.moveTo(...coordinates[0]);
  ctx.beginPath();
  for (let i = 0; i < coordinates.length; i++) {
    ctx.lineTo(...coordinates[i]);
  }
  ctx.closePath();
  ctx.stroke();
}

function getMaxMin() {
  const x = coordinates.map(e => e[0]);
  const y = coordinates.map(e => e[1]);

  return {
    maxX: Math.max(...x),
    minX: Math.min(...x),
    maxY: Math.max(...y),
    minY: Math.min(...y),
  }
}

function getCenter() {
  const maxMin = getMaxMin();
  return [(maxMin.maxX + maxMin.minX) / 2, (maxMin.minY + maxMin.maxY) / 2];
}

/**
 * 绘制文本内容
 */
function drawText() {
  const maxMin = getMaxMin();
  ctx.save();
  ctx.translate((maxMin.maxX + maxMin.minX) / 2, (maxMin.maxY + maxMin.minY) / 2);
  ctx.font = '20px serif';
  ctx.fillStyle = '#333';
  ctx.textAlign = 'center';
  ctx.fillText('测试Canvas缩放功能', 0, 0, maxMin.maxX - maxMin.minX);
  ctx.restore();
}

/**
 * 通过改变canvas的样式来实现缩放,原理是通过canvas的style中height和width 以及自带的width和height属性不同的比例实现缩放
 * flag true:放大 false: 缩小
 */
function zoom1(flag = true) {
  const curH = Number(canvas.getAttribute('height'));
  const curW = Number(canvas.getAttribute('width'));
  canvas.setAttribute('width', flag ? curW * scale : curW / scale);
  canvas.setAttribute('height', flag ? curH * scale : curH / scale);
}

/**
 * 通过改变canvas的样式来实现缩放,原理是通过canvas的scale属性缩放横纵轴
 * 优点:代码量少
 * 缺点:会改变数据的原始属性
 * flag true:放大 false: 缩小
 */
function zoom2(flag = true) {
  const beta = flag ? 1 / scale : scale;
  ctx.scale(beta, beta);
}

/**
 * 通过改变坐标点信息实现缩放,可以设置缩放的中心点
 * 优点:代码量较大,需要修改每个要素的坐标点
 * 缺点:会改变数据的原始属性
 * flag true:放大 false: 缩小
 */
function zoom3(flag = true, event) {
  // 记录当前鼠标的相对位置
  initX = event.offsetX;
  initY = event.offsetY;
  // 计算放大缩小的比例
  multi = 1 * (flag ? 1 / scale : scale);

  // 获取图形中心点
  const center = getCenter();
  // 获取图形最大最小的顶点坐标
  const maxMin = getMaxMin();
  // 计算缩放后的中心点坐标
  const newC = [center[0] - (initX - center[0]) * (multi - 1), center[0] - (initY - center[1]) * (multi - 1)];

  // 根据缩放后的中心点计算各个顶点信息
  coordinates = coordinates.map(e => {
    const operateX = center[0] - e[0] > 0 ? 1 : -1;
    const operateY = center[1] - e[1] > 0 ? 1 : -1;
    return [newC[0] - (maxMin.maxX - maxMin.minX) * multi / 2 * operateX, newC[1] - (maxMin.maxY - maxMin.minY) * multi / 2 * operateY];
  })
}

function addEvent() {
  ctx.beginPath();
  ctx.arc(0, 0, 5, 0, Math.PI * 2);
  ctx.stroke();
  const minMax = getMaxMin(coordinates);
  canvas.addEventListener('mousewheel', event => {
    ctx.clearRect(0, 0, 1600, 1600);
    // zoom1(event.wheelDelta > 0);

    // zoom2(event.wheelDelta > 0);
    
    zoom3(event.wheelDelta > 0, event);
    draw();
    drawText();
    
    ctx.restore();
  });
}

export default { init, addEvent };
View Code