Konva画布缩放

滚动鼠标,可以缩放画布

<template>
  <div class="rect">
    <div id="canvas"></div> <!-- 画布容器 -->
  </div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue'; // 导入 Vue 的 onMounted 钩子
import Konva from 'konva'; // 导入 Konva 图形库

// 声明 stage、layer 和 transformer
let stage: Konva.Stage | null = null; // 初始化 stage 为 null
const layer: Konva.Layer = new Konva.Layer(); // 创建一个新的图层
const tr: Konva.Transformer = new Konva.Transformer(); // 创建一个 Transformer 用于选中和调整形状

// 在组件挂载后初始化
onMounted(() => {
  init(); // 调用初始化函数
});

const init = () => {
  const el = document.getElementById("canvas"); // 获取画布 DOM 元素
  if (!el) {
    return; // 如果没有找到画布元素,结束函数
  }


  const { clientWidth, clientHeight } = el; // 获取画布的宽度和高度

  // 创建一个 Konva Stage 平台
  stage = new Konva.Stage({
    container: 'canvas', // 指定画布容器
    width: clientWidth, // 设置宽度
    height: clientHeight, // 设置高度
  });

  stage.add(layer); // 将层添加到舞台

  // 创建第一个矩形
  const rect1 = new Konva.Rect({
    name: "rect", // 给图形添加名称
    x: clientWidth / 2 - 100, // 设置 x 坐标
    y: clientHeight / 2, // 设置 y 坐标
    width: 200, // 矩形的宽度
    height: 100, // 矩形的高度
    fill: "#ff8800", // 填充颜色
    stroke: 'black', // 边框颜色
    strokeWidth: 1, // 边框宽度
    draggable: true, // 设置可拖拽
  });
  layer.add(rect1); // 将第一个矩形添加到图层

  // 创建第二个矩形
  const rect2 = new Konva.Rect({
    name: "rect", // 同样的名称,可能导致选择问题
    x: clientWidth / 2 + 100, // 设置 x 坐标
    y: clientHeight / 2, // 设置 y 坐标
    width: 200, // 矩形的宽度
    height: 100, // 矩形的高度
    fill: "#ff00ff", // 填充颜色
    stroke: 'black', // 边框颜色
    strokeWidth: 1, // 边框宽度
    draggable: true, // 设置可拖拽
  });
  layer.add(rect2); // 将第二个矩形添加到图层

  // 新增一个矩形用于框选功能
  const selectionRect = new Konva.Rect({
    fill: "rgba(0,0,255,0.1)", // 填充颜色
    visible: false, // 初始时隐藏
    stroke: "rgba(0,0,255,0.5)", // 边框颜色
    strokeWidth: 1, // 边框宽度
  });
  layer.add(selectionRect); // 将选择矩形添加到图层

  layer.add(tr); // 将 Transformer 添加到图层

  // 点击图形时进行编辑,点击画布则取消编辑
  stage.on("click tap", (e) => {
    const dom = e.target; // 获取点击的目标
    if (dom.getType() === "Shape") { // 判断目标是否为形状
      tr.nodes([dom]); // 选中该形状
    } else {
      tr.nodes([]); // 如果点击的是画布,则清空选中
    }
  });

  let x1 = 0, y1 = 0, x2 = 0, y2 = 0; // 初始化坐标变量

  // 鼠标按下时开始框选
  stage.on("mousedown touchstart", (e) => {
    if (e.target !== stage) { // 如果点击的不是画布
      return; // 结束函数
    }
    e.evt.preventDefault(); // 阻止默认行为

    // 获取鼠标位置
    const { x, y } = stage.getRelativePointerPosition() as Konva.Vector2d;
    x1 = x; // 记录起始点 x 坐标
    x2 = x; // 记录起始点 x 坐标
    y1 = y; // 记录起始点 y 坐标
    y2 = y; // 记录起始点 y 坐标
    selectionRect.visible(true); // 显示选择框
    selectionRect.width(0); // 初始化宽度为 0
    selectionRect.height(0); // 初始化高度为 0
  });

  // 鼠标移动时更新选择框
  stage.on("mousemove touchmove", (e) => {
    if (!selectionRect.visible()) { // 如果选择框不可见
      return; // 结束函数
    }
    const { x, y } = stage.getPointerPosition() as Konva.Vector2d; // 获取当前鼠标位置
    x2 = x; // 更新结束点 x 坐标
    y2 = y; // 更新结束点 y 坐标

    // 更新选择矩形的属性
    selectionRect.setAttrs({
      x: Math.min(x1, x2), // 设置选择框的 x 坐标
      y: Math.min(y1, y2), // 设置选择框的 y 坐标
      width: Math.abs(x1 - x2), // 设置选择框的宽度
      height: Math.abs(y1 - y2), // 设置选择框的高度
    });
  });

  // 鼠标松开时完成选择
  stage.on("mouseup touchend", (e) => {
    if (!stage || !selectionRect.visible()) { // 如果舞台不存在或选择框不可见
      return; // 结束函数
    }

    // 隐藏选择框
    selectionRect.visible(false);

    // 获取所有矩形形状并进行选择
    const shapes = stage.find(".rect"); // 查找所有矩形
    const box = selectionRect.getClientRect(); // 获取选择框的边界
    const selected = shapes.filter((shape) =>
      Konva.Util.haveIntersection(box, shape.getClientRect()) // 判断是否与选择框相交
    );
    tr.nodes(selected); // 将选中的形状传给 Transformer
  });
  /缩放/
  const SCALE_BY = 1.1; // 缩放倍数

  stage.on("wheel", (e) => {
    console.log('wheel event triggered'); // 检查事件触发
    e.evt.preventDefault(); // 阻止默认的页面滚动

    if (!stage) return; // 如果 stage 没有初始化,则退出

    // 获取当前舞台的缩放比例
    const oldScale = stage.scaleX();

    // 获取鼠标指针在舞台坐标系内的位置
    const pointer = stage.getPointerPosition() as Konva.Vector2d;
    if (!pointer) return; // 如果鼠标位置无效,退出

    // 计算当前鼠标点在缩放前的相对位置
    const mousePointTo = {
      x: (pointer.x - stage.x()) / oldScale,
      y: (pointer.y - stage.y()) / oldScale,
    };

    // 判断滚轮方向:deltaY > 0 表示缩小,< 0 表示放大
    let direction = e.evt.deltaY > 0 ? -1 : 1;

    // 支持 ctrl 键反转缩放方向(可选功能)
    if (e.evt.ctrlKey) {
      direction = -direction;
    }

    // 根据滚轮方向计算新的缩放比例
    const newScale = direction > 0 ? oldScale * SCALE_BY : oldScale / SCALE_BY;

    // 更新舞台的缩放比例
    stage.scale({ x: newScale, y: newScale });

    // 计算缩放后舞台新的位置,以保持鼠标相对位置不变
    const newPos = {
      x: pointer.x - mousePointTo.x * newScale,
      y: pointer.y - mousePointTo.y * newScale,
    };

    // 设置舞台的新位置
    stage.position(newPos);

    // 使用 batchDraw 来优化更新频率
    stage.batchDraw();
  });


}
</script>

<style scoped lang="scss">
.rect {
  padding: 20px; // 设置外边距

  #canvas {
    background-color: #eee; // 设置背景颜色
    border: 1px solid #666; // 设置边框
    height: calc(100vh - 42px); // 设置画布高度
  }
}
</style>

上一篇:repo将每个仓库回退到第一个commit的状态


下一篇:@tarojs/components 和 taro-ui 中的组件之间的区别