fabric实现画布的平移

一、效果图

在这里插入图片描述

二、实现思路

1、点击平移按钮时,切换鼠标样式,并禁止object元素选中,反之亦然

//切换操作切换
    panChange() {
      this.isPan = !this.isPan;
      if (this.isPan) {
        this.enablePan();
      } else {
        this.disablePan();
      }
    },
    enablePan() {
      this.fabricCanvas.discardActiveObject(); //取消当前的选中状态
      //禁止页面的选择
      this.fabricCanvas.forEachObject(function(obj) {
        obj.selectable = false; // 禁用对象选择
        obj.evented = false; // 禁用对象事件
      });
      // 设置 canvas 的鼠标样式为 'move'
      this.fabricCanvas.defaultCursor = "grab";
      this.fabricCanvas.hoverCursor = "grab";
      // 添加事件监听
      this.fabricCanvas.on("mouse:down", this.onMouseDown);
      this.fabricCanvas.on("mouse:move", this.onMouseMove);
      this.fabricCanvas.on("mouse:up", this.onMouseUp);
      this.fabricCanvas.on("mouse:out", this.onMouseUp);

      this.fabricCanvas.renderAll(); // 重绘画布以显示更改
    },
    disablePan() {
      //禁止页面的选择
      this.fabricCanvas.forEachObject(function(obj) {
        obj.selectable = true; // 禁用对象选择
        obj.evented = true; // 禁用对象事件
      });
      // 恢复 canvas 的默认鼠标样式
      this.fabricCanvas.defaultCursor = "default";
      this.fabricCanvas.hoverCursor = "default";

      // 移除事件监听
      this.fabricCanvas.off("mouse:down", this.onMouseDown);
      this.fabricCanvas.off("mouse:move", this.onMouseMove);
      this.fabricCanvas.off("mouse:up", this.onMouseUp);
      this.fabricCanvas.off("mouse:out", this.onMouseUp);

      this.fabricCanvas.renderAll(); // 重绘画布以显示更改
    },

2、当是拖动状态时,添加鼠标平移操作,并限制了边界条件

 onMouseDown(event) {
      this.isDragging = true;
      this.lastPosX = event.e.clientX;
      this.lastPosY = event.e.clientY;
      this.fabricCanvas.defaultCursor = "grabbing";
    },
    // onMouseMove(event) {
    //   if (this.isDragging) {
    //     const e = event.e;
    //     const vpt = this.fabricCanvas.viewportTransform;
    //     vpt[4] += e.clientX - this.lastPosX;
    //     vpt[5] += e.clientY - this.lastPosY;
    //     this.fabricCanvas.requestRenderAll();
    //     this.lastPosX = e.clientX;
    //     this.lastPosY = e.clientY;
    //   }
    // },
    onMouseMove(event) {
      if (this.isDragging) {
        const e = event.e;
        const vpt = this.fabricCanvas.viewportTransform.slice(); // 创建副本,避免直接修改
        const zoom = this.fabricCanvas.getZoom();

        // 计算平移的距离
        const moveX = e.clientX - this.lastPosX;
        const moveY = e.clientY - this.lastPosY;

        // 获取画布和容器的宽高
        const canvasWidth = this.fabricCanvas.getWidth();
        const canvasHeight = this.fabricCanvas.getHeight();
        const containerWidth = this.canvasProp.width;
        const containerHeight = this.canvasProp.height;

        // 计算缩放后的宽高
        const scaledWidth = canvasWidth * zoom;
        const scaledHeight = canvasHeight * zoom;

        // 计算边界
        const leftBoundary = Math.min(0, containerWidth - scaledWidth);
        const topBoundary = Math.min(0, containerHeight - scaledHeight);

        // 应用平移限制
        vpt[4] = Math.max(leftBoundary, Math.min(0, vpt[4] + moveX));
        vpt[5] = Math.max(topBoundary, Math.min(0, vpt[5] + moveY));

        // 更新画布视口变换
        this.fabricCanvas.setViewportTransform(vpt);
        this.lastPosX = e.clientX;
        this.lastPosY = e.clientY;
      }
    },
    onMouseUp() {
      this.isDragging = false;
      this.fabricCanvas.defaultCursor = "grab";
    },

三、完整代码

<template>
  <div
    :style="{
      width: canvasProp.width + 'px',
      height: canvasProp.height + 'px',
      border: '1px solid #e5e5e5'
    }"
  >
    <canvas
      :width="canvasProp.width"
      :height="canvasProp.height"
      ref="canvas"
      :style="{
        width: canvasProp.width + 'px',
        height: canvasProp.height + 'px'
      }"
      id="canvasId"
    ></canvas>
    <div class="muane">
      <span @click="zoomBig">
        <i class="el-icon-zoom-in" style="color: #000;font-size: 16px;"></i>
      </span>
      <span @click="zoomSmall">
        <i class="el-icon-zoom-out" style="color: #000;font-size: 16px;"></i>
      </span>
      <span @click="panChange">
        <i
          v-if="!isPan"
          class="icon iconfont-saber icon-saber-shouzhang mIcon"
          style="color: #000;font-size: 15px !important;"
        ></i>
        <i
          v-else
          class="icon iconfont-saber icon-saber-24gl-pointer mIcon"
          style="color: #000;font-size: 16px !important;"
        ></i>
      </span>
    </div>
  </div>
</template>
<script>
let backgroundImage = null;
export default {
  name: "images-tags",
  props: {
    // 矩形标注的数据
    tagsData: {
      type: Array,
      default: () => {
        return [
          {
            label: "基表数据",
            color: "#0000ff",
            type: "rectangle",
            width: 150,
            height: 50,
            rotate: 20,
            isInit: true,
            startX: 185,
            startY: 235
          },
          {
            label: "数据点2",
            color: "#0000ff",
            type: "rectangle",
            width: 150,
            height: 50,
            rotate: 0,
            isInit: false,
            startX: 100,
            startY: 100
          }
        ];
      }
    },
    // 图片路径
    images: {
      type: String,
      default: "/img/yejing1.jpg"
    }
  },
  data() {
    return {
      fabricCanvas: null,
      isPan: false,
      isDragging: false,
      lastPosX: 0,
      lastPosY: 0,
      canvasProp: {
        width: 0, // canvas的宽度
        height: 0, // canvas的高度
        translateX: 0,
        translateY: 0
      }
    };
  },
  mounted() {
    this.loadImageAndSetCanvas();
    //window.addEventListener("keydown", this.handleKeyDown);
  },
  beforeDestroy() {
    //window.removeEventListener("keydown", this.handleKeyDown);
  },
  methods: {
    //切换操作切换
    panChange() {
      this.isPan = !this.isPan;
      if (this.isPan) {
        this.enablePan();
      } else {
        this.disablePan();
      }
    },
    enablePan() {
      this.fabricCanvas.discardActiveObject(); //取消当前的选中状态
      //禁止页面的选择
      this.fabricCanvas.forEachObject(function(obj) {
        obj.selectable = false; // 禁用对象选择
        obj.evented = false; // 禁用对象事件
      });
      // 设置 canvas 的鼠标样式为 'move'
      this.fabricCanvas.defaultCursor = "grab";
      this.fabricCanvas.hoverCursor = "grab";
      // 添加事件监听
      this.fabricCanvas.on("mouse:down", this.onMouseDown);
      this.fabricCanvas.on("mouse:move", this.onMouseMove);
      this.fabricCanvas.on("mouse:up", this.onMouseUp);
      this.fabricCanvas.on("mouse:out", this.onMouseUp);

      this.fabricCanvas.renderAll(); // 重绘画布以显示更改
    },
    disablePan() {
      //禁止页面的选择
      this.fabricCanvas.forEachObject(function(obj) {
        obj.selectable = true; // 禁用对象选择
        obj.evented = true; // 禁用对象事件
      });
      // 恢复 canvas 的默认鼠标样式
      this.fabricCanvas.defaultCursor = "default";
      this.fabricCanvas.hoverCursor = "default";

      // 移除事件监听
      this.fabricCanvas.off("mouse:down", this.onMouseDown);
      this.fabricCanvas.off("mouse:move", this.onMouseMove);
      this.fabricCanvas.off("mouse:up", this.onMouseUp);
      this.fabricCanvas.off("mouse:out", this.onMouseUp);

      this.fabricCanvas.renderAll(); // 重绘画布以显示更改
    },
    onMouseDown(event) {
      this.isDragging = true;
      this.lastPosX = event.e.clientX;
      this.lastPosY = event.e.clientY;
      this.fabricCanvas.defaultCursor = "grabbing";
    },
    // onMouseMove(event) {
    //   if (this.isDragging) {
    //     const e = event.e;
    //     const vpt = this.fabricCanvas.viewportTransform;
    //     vpt[4] += e.clientX - this.lastPosX;
    //     vpt[5] += e.clientY - this.lastPosY;
    //     this.fabricCanvas.requestRenderAll();
    //     this.lastPosX = e.clientX;
    //     this.lastPosY = e.clientY;
    //   }
    // },
    onMouseMove(event) {
      if (this.isDragging) {
        const e = event.e;
        const vpt = this.fabricCanvas.viewportTransform.slice(); // 创建副本,避免直接修改
        const zoom = this.fabricCanvas.getZoom();

        // 计算平移的距离
        const moveX = e.clientX - this.lastPosX;
        const moveY = e.clientY - this.lastPosY;

        // 获取画布和容器的宽高
        const canvasWidth = this.fabricCanvas.getWidth();
        const canvasHeight = this.fabricCanvas.getHeight();
        const containerWidth = this.canvasProp.width;
        const containerHeight = this.canvasProp.height;

        // 计算缩放后的宽高
        const scaledWidth = canvasWidth * zoom;
        const scaledHeight = canvasHeight * zoom;

        // 计算边界
        const leftBoundary = Math.min(0, containerWidth - scaledWidth);
        const topBoundary = Math.min(0, containerHeight - scaledHeight);

        // 应用平移限制
        vpt[4] = Math.max(leftBoundary, Math.min(0, vpt[4] + moveX));
        vpt[5] = Math.max(topBoundary, Math.min(0, vpt[5] + moveY));

        // 更新画布视口变换
        this.fabricCanvas.setViewportTransform(vpt);
        this.lastPosX = e.clientX;
        this.lastPosY = e.clientY;
      }
    },
    onMouseUp() {
      this.isDragging = false;
      this.fabricCanvas.defaultCursor = "grab";
    },
    zoomBig() {
      const currentZoom = this.fabricCanvas.getZoom();
      if (currentZoom < 3) {
        this.scaleCanvas(currentZoom + 0.1);
      }
    },
    zoomSmall() {
      const currentZoom = this.fabricCanvas.getZoom();
      if (currentZoom > 1) {
        this.scaleCanvas(currentZoom - 0.1);
      }
    },
    scaleCanvas(scale) {
      const center = this.getCanvasCenter();
      this.fabricCanvas.zoomToPoint({ x: center.x, y: center.y }, scale);
    },
    //获取画布的中心点
    getCanvasCenter() {
      const canvasCenter = {
        x: this.fabricCanvas.width / 2,
        y: this.fabricCanvas.height / 2
      };
      return canvasCenter;
    },

    handleKeyDown(event) {
      console.log("event.key", event.key);
      const step = 5; // 每次移动的步长
      switch (event.key) {
        case "ArrowUp":
          this.canvasProp.translateY -= step;
          break;
        case "ArrowDown":
          this.canvasProp.translateY += step;
          break;
        case "ArrowLeft":
          this.canvasProp.translateX -= step;
          break;
        case "ArrowRight":
          this.canvasProp.translateX += step;
          break;
      }
      this.panCanvas(this.canvasProp.translateX, this.canvasProp.translateY);
    },
    loadFromJSON(json) {
      this.fabricCanvas.clear();
      //背景图替换为当前的背景图
      let newjson = JSON.parse(json);

      console.log("加载的json", newjson);
      newjson.backgroundImage = backgroundImage;
      this.fabricCanvas.setBackgroundImage();
      //this.fabricCanvas.loadFromJSON(newjson);
      // 反序列化对象
      this.fabricCanvas.loadFromJSON(newjson);
    },
    getCanvasJson() {
      console.log("在线获取json", this.fabricCanvas.toJSON(["id"]));
      return JSON.stringify(this.fabricCanvas.toJSON(["id"]));
    },

    hexToRgba(hex, alpha) {
      const bigint = parseInt(hex.replace("#", ""), 16);
      const r = (bigint >> 16) & 255;
      const g = (bigint >> 8) & 255;
      const b = bigint & 255;
      return `rgba(${r},${g},${b},${alpha})`;
    },
    // ...其他方法
    panCanvas(translateX, translateY) {
      // 获取当前的 viewportTransform
      const viewportTransform = this.fabricCanvas.viewportTransform.slice(); // 创建一个副本,以免直接修改原始数组

      // 更新平移值
      viewportTransform[4] = translateX;
      viewportTransform[5] = translateY;

      // 设置新的 viewportTransform
      this.fabricCanvas.setViewportTransform(viewportTransform);
    },
    loadImageAndSetCanvas() {
      const img = new Image();
      img.src = this.images;
      img.onload = () => {
        console.log("图片加载完毕", img);
        this.canvasProp.width = img.width;
        this.canvasProp.height = img.height;
        this.$nextTick(() => {
          this.fabricCanvas = new fabric.Canvas("canvasId");
          // 设置canvas大小
          this.fabricCanvas.setWidth(img.width);
          this.fabricCanvas.setHeight(img.height);

          // 创建 Fabric 图片对象
          const fabricImage = new fabric.Image(img, {
            left: 0,
            top: 0,
            selectable: false // 防止背景图片被选择
          });

          // 设置背景图片
          let backgroundImageObj = this.fabricCanvas.setBackgroundImage(
            fabricImage
          );
          backgroundImage = backgroundImageObj.backgroundImage;
        });

        //this.drawTags(this.tagsData[0])
      };
    },
    clear() {
      this.fabricCanvas.getObjects().forEach(obj => {
        if (obj !== this.fabricCanvas.backgroundImage) {
          this.fabricCanvas.remove(obj);
        }
      });
    },
    getGroupById(id) {
      let result = null;
      this.fabricCanvas.getObjects().forEach(obj => {
        console.log(obj);
        if (obj.id === id) {
          result = obj;
        }
      });
      return result;
    },
    remove(item) {
      console.log("id", item.id);

      let result = this.getGroupById(item.id);

      console.log("result", result);

      if (result) {
        this.fabricCanvas.remove(result);
      }
    },
    drawTags(item, callback = () => {}) {
      //this.clear(); // 清空所有对象
      // tagsData.forEach(item => {
      // 创建一个矩形
      const rect = new fabric.Rect({
        fill: this.hexToRgba(item.color, 0.2), // 填充颜色,透明度为 0.2
        width: item.width, // 矩形的宽度
        height: item.height, // 矩形的高度
        angle: 0, // 旋转角度
        stroke: item.color,
        strokeWidth: 2 // 边框宽度
      });

      // 创建第一个文字对象
      const text1 = new fabric.Text(item.label, {
        fontFamily: "Arial",
        fontSize: 20,
        fill: item.color // 文字颜色
        // stroke: "#ffffff", // 文字边框颜色
        // strokeWidth: 0.5 // 文字边框宽度
      });

      // 计算文字位置以确保其在矩形的正中央
      text1.set({
        left: rect.left + rect.width / 2,
        top: rect.top + rect.height / 2,
        originX: "center",
        originY: "center"
      });

      // 使用fabric.Group将矩形和文字组合在一起
      const group = new fabric.Group([rect, text1], {
        left: item.isInit
          ? this.canvasProp.width / 2 - item.width / 2
          : item.startX, // 矩形的左上角 x 坐标
        top: item.isInit
          ? this.canvasProp.height / 2 - item.height / 2
          : item.startY, // 矩形的左上角 y 坐标
        cornerColor: item.color, // 控制点的颜色
        cornerSize: 10, // 控制点的大小
        borderWidth: 0, // 选中时的边框宽度
        transparentCorners: true,
        angle: item.rotate, // 组合对象的旋转角度
        id: item.id
      });
      // 将组合添加到画布上
      console.log("添加到画布上", group);
      this.fabricCanvas.add(group);
      callback();
      //});
    },
    saveData() {
      this.getPointData();
    },
    getPointData() {
      let result = {};
      this.fabricCanvas.getObjects().forEach(rect => {
        console.log("rect==", rect);
        const coords = [];
        const points = rect.get("aCoords"); // 获取矩形的绝对坐标

        console.log("points", points);

        const viewportTransform = this.fabricCanvas.viewportTransform;
        const zoom = this.fabricCanvas.getZoom();

        // 将内部坐标转换为实际画布坐标
        Object.keys(points).forEach(key => {
          let point = points[key];
          const actualX = (point.x - viewportTransform[4]) / zoom;
          const actualY = (point.y - viewportTransform[5]) / zoom;
          coords.push([Math.round(actualX), Math.round(actualY)]);
        });

        result[rect.id] = coords;
      });
      console.log("result", result);
      return result;
    }
  }
};
</script>
<style lang="scss" scoped>
.muane {
  height: 36px;
  background: #fafafa;
  display: flex;
  align-items: center;
  border-left: 1px solid #e5e5e5;
  border-right: 1px solid #e5e5e5;
  border-bottom: 1px solid #e5e5e5;
  span {
    width: 50%;
    text-align: center;
  }
}
</style>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值