fabric.js 实现元素拖拽、引入图片、标注交互

这篇具有很好参考价值的文章主要介绍了fabric.js 实现元素拖拽、引入图片、标注交互。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

by:垃圾程序员

零、前言

特别鸣谢:拿只键盘出来绣花的德育处主任,他的系列文章给了我很大的帮助。该说不说,站在前人的肩膀上就是得劲。

德育处主任 - 知乎拿只键盘出来绣花 回答数 7,获得 143 次赞同https://www.zhihu.com/people/rabbit-svip

fabric.js 是一个用于创建可交互式的 HTML5 canvas 应用程序的开源 JavaScript 库,它提供了一套简单、易用的 API,可以快速地实现各种图形操作和动画效果。使用 fabric.js,你可以轻松地创建文本、图像、形状、路径等多种元素,并对它们进行缩放、旋转、位移、剪切、合并等操作。

fabric.js 具有以下特点:

  1. 简单易用:fabric.js 的 API 非常简单、易于理解和使用,即使是初学者也能够快速上手。

  2. 功能强大:fabric.js 提供了各种基本图形元素的创建和操作方法,同时还支持高级功能,如复合对象、滤镜、选区、事件处理、动画等。

  3. 跨浏览器兼容:fabric.js 支持多种主流浏览器,包括 Chrome、Firefox、Safari、Edge 和 IE 等。

  4. 开源免费:fabric.js 是完全开源的,你可以自由地使用、修改和分发它。

最终实现效果:

fabric循环标注,fabric,图像处理,canva可画

一、引入 fabric.js 库

1.1npm 安装

npm install fabric --save

1.2全局引入

import { fabric } from 'fabric'
Vue.use(fabric);

二、页面布局

2.1创建左右结构的布局

<template>
  <div class="mainDiv rowflex">
    <!-- 左侧元素区 -->
    <div class="leftFiv columnflex" style="justify-content: start;">
      <!-- 循环所有图标 -->
      <div class="element columnflex" v-for="(item, index) in elementList" :key="index">
        <img class="element_item" :src="item.address" @dragstart="handleDragStart(index)" />
      </div>
    </div>
    <!-- 画布区 -->
    <div id="rightDiv" class="rightDiv">
      <canvas id="fabric"></canvas>
    </div>
    <!-- 右键菜单 -->
    <div id="menu" class="menu-x" v-show="menuDisplay">
      <div class="menu-li" @click="bindingIdentifier()">绑定唯一标识</div>
      <div class="menu-li" @click="deleteElement()">删除</div>
    </div>
  </div>
</template>

2.2画布初始化并加载底图

//初始化画布
      initCanvas() {
        // 获取容器元素,得到父容器的宽和高
        const container = document.getElementById('rightDiv');
        // 创建一个与容器宽高一致的 canvas 对象
        this.canvas = new fabric.Canvas("fabric", {
          width: container.clientWidth,
          height: container.clientHeight,
          fireRightClick: true, // 启用右键,button的数字为3
          stopContextMenu: true, // 禁止默认右键菜单
        })
        // 设置画布的背景图片
        this.setBackground(container.clientWidth, container.clientHeight)
        //监听元素是否被下放到画布上
        this.elementPlacement()
        //监听画布拖拽,包含三个监听事件
        this.canvasDragAndDrop()
        //监听画布缩放
        this.canvasZoom()
      },
      //设置画布的背景图片
      setBackground(clientWidth, clientHeight) {
        fabric.Image.fromURL(this.canvasBackgroundImage, img => {
          // 缩放背景图片以适应画布
          // 如果背景图片宽度或高度大于画布宽度或高度
          if (img.width > clientWidth || img.height > clientHeight) {
            // 计算缩放比例
            let scaleX = clientWidth / img.width;
            let scaleY = clientHeight / img.height;
            let scale = Math.min(scaleX, scaleY);
            // 缩放背景图片
            img.scaleToWidth(img.width * scale);
            img.scaleToHeight(img.height * scale);
          } else { // 如果背景图片宽度和高度都小于或等于画布宽度和高度
            // 计算背景图片在画布中的位置
            img.left = (clientWidth - img.width) / 2;
            img.top = (clientHeight - img.height) / 2;
          }
          this.canvas.setBackgroundImage(img, this.canvas.renderAll.bind(this.canvas));
        });
      },

效果如下:

fabric循环标注,fabric,图像处理,canva可画

三、实现画布缩放和移动

3.1画布缩放

//监听画布缩放
      canvasZoom() {
        this.canvas.on('mouse:wheel', opt => {
          const delta = opt.e.deltaY // 滚轮,向上滚一下是 -100,向下滚一下是 100
          let zoom = this.canvas.getZoom() // 获取画布当前缩放值
          zoom *= 0.999 ** delta
          if (zoom > 20) zoom = 20 // 限制最大缩放级别
          if (zoom < 0.01) zoom = 0.01 // 限制最小缩放级别
          // 以鼠标所在位置为原点缩放
          this.canvas.zoomToPoint({ // 关键点
              x: opt.e.offsetX,
              y: opt.e.offsetY
            },
            zoom // 传入修改后的缩放级别
          )
        })
      },

3.2画布移动

// 监听画布拖拽,同时也监听了右键菜单
      canvasDragAndDrop() {
        // 按下鼠标事件
        this.canvas.on("mouse:down", opt => {
          var evt = opt.e
          // 判断:右键,且在元素上右键
          // opt.button: 1-左键;2-中键;3-右键
          // 在画布上点击:opt.target 为 null
          if (opt.button === 3 && opt.target) {
            this.lastMenu = opt.target
            let menu = document.getElementById('menu');
            // 禁止在菜单上的默认右键事件
            menu.oncontextmenu = function(e) {
              e.preventDefault()
            }
            // 显示菜单,设置右键菜单位置
            // 获取菜单组件的宽高
            //这个地方是我自己设置的宽和高计算的,如果你之后复制过去需要修改一下
            const menuWidth = 120
            const menuHeight = menu.childNodes.length * 40
            // 当前鼠标位置
            let pointX = opt.pointer.x
            let pointY = opt.pointer.y
            // 计算菜单出现的位置
            // 如果鼠标靠近画布底部,菜单就出现在鼠标指针上方
            if (this.canvas.height - pointY <= menuHeight) {
              pointY -= menuHeight
            }
            menu.style = `
                  visibility: visible;
                  left: ${pointX}px;
                  top: ${pointY}px;
                  z-index: 100;
                `
            // 将菜单展示
            this.menuDisplay = true
          } else {
            // 将菜单隐藏
            this.menuDisplay = false
          }
          //拖拽
          if (evt.shiftKey === true) {
            this.isDragging = true
            this.canvas.selection = false;
          }
        });
        // 移动鼠标事件
        this.canvas.on("mouse:move", opt => {
          if (this.isDragging && opt && opt.e) {
            var delta = new fabric.Point(opt.e.movementX, opt.e.movementY);
            this.canvas.relativePan(delta);
          }
        });
        // 松开鼠标事件
        this.canvas.on("mouse:up", opt => {
          this.isDragging = false;
          this.canvas.selection = true;
        });
      },

四、实现拖拽元素到画布

4.1监听拖到画布上

 //监听元素是否被下放到画布上
      elementPlacement() {
        this.canvas.on('drop', elt => {
          // 画布元素距离浏览器左侧和顶部的距离
          let offset = {
            left: this.canvas.getSelectionElement().getBoundingClientRect().left,
            top: this.canvas.getSelectionElement().getBoundingClientRect().top
          }
          // 鼠标坐标转换成画布的坐标(未经过缩放和平移的坐标)
          let point = {
            x: elt.e.x - offset.left,
            y: elt.e.y - offset.top,
          }
          // 转换后的坐标,restorePointerVpt 不受视窗变换的影响
          let pointerVpt = this.canvas.restorePointerVpt(point)
          //创建元素
          this.createElement(this.imageAddress, pointerVpt)
        });
      },

4.2生成元素

 //在画布上生成拖拽过来的元素
      createElement(imageAddress, pointerVpt) {
        fabric.Image.fromURL(imageAddress, oImg => {
          //这个地方做了一下偏移,让鼠标位置为图标的中心,真实的位置信息要还原回去
          oImg.top = pointerVpt.y - 24
          oImg.left = pointerVpt.x - 24
          this.canvas.add(oImg)
        })
      },

五、实现元素右键交互

5.2删除画布元素

//删除元素
      deleteElement() {
        this.canvas.remove(this.lastMenu)
        // 将菜单隐藏
        this.menuDisplay = false
      },

5.3弹窗输入编码

      //设定唯一标识
      bindingIdentifier() {
        this.$prompt('请输入唯一编码', '编辑', {
          confirmButtonText: '确定',
          cancelButtonText: '取消'
        }).then(({
          value
        }) => {
          this.$message({
            type: 'success',
            message: '你的唯一编码是: ' + value
          });
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '取消输入'
          });
        });
        // 将菜单隐藏
        this.menuDisplay = false
      }

完整代码:

<template>
  <div class="mainDiv rowflex">
    <!-- 左侧元素区 -->
    <div class="leftFiv columnflex" style="justify-content: start;">
      <!-- 循环所有图标 -->
      <div class="element columnflex" v-for="(item, index) in elementList" :key="index">
        <img class="element_item" :src="item.address" @dragstart="handleDragStart(index)" />
      </div>
    </div>
    <!-- 画布区 -->
    <div id="rightDiv" class="rightDiv">
      <canvas id="fabric"></canvas>
    </div>
    <!-- 右键菜单 -->
    <div id="menu" class="menu-x" v-show="menuDisplay">
      <div class="menu-li" @click="bindingIdentifier()">绑定唯一标识</div>
      <div class="menu-li" @click="deleteElement()">删除</div>
    </div>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        // 图标列表
        elementList: [{
            key: 'ancientTrees',
            address: require('@/assets/icons/fabric/ancientTrees.png')
          },
          {
            key: 'camera',
            address: require('@/assets/icons/fabric/camera.png')
          },
          {
            key: 'factory',
            address: require('@/assets/icons/fabric/factory.png')
          },
          {
            key: 'fireFighting',
            address: require('@/assets/icons/fabric/fireFighting.png')
          },
          {
            key: 'house',
            address: require('@/assets/icons/fabric/house.png')
          },
          {
            key: 'hydrology',
            address: require('@/assets/icons/fabric/hydrology.png')
          },
        ],
        // 画布背景图片
        canvasBackgroundImage: require('@/assets/icons/fabric/canvasImg.jpg'),
        // 画布
        canvas: null,
        //最后一次拖动的图片
        imageAddress: '',
        //是否拖动中
        isDragging: false,
        //最后的x轴方向的位置
        lastPosX: 0,
        //最后的y轴方向的位置
        lastPosY: 0,
        //菜单是否显示
        menuDisplay: false,
        //最后一次右键选中的元素
        lastMenu: null
      }
    },
    mounted() {
      this.initCanvas();
    },
    methods: {
      //初始化画布
      initCanvas() {
        // 获取容器元素,得到父容器的宽和高
        const container = document.getElementById('rightDiv');
        // 创建一个与容器宽高一致的 canvas 对象
        this.canvas = new fabric.Canvas("fabric", {
          width: container.clientWidth,
          height: container.clientHeight,
          fireRightClick: true, // 启用右键,button的数字为3
          stopContextMenu: true, // 禁止默认右键菜单
        })
        // 设置画布的背景图片
        this.setBackground(container.clientWidth, container.clientHeight)
        //监听元素是否被下放到画布上
        this.elementPlacement()
        //监听画布拖拽,包含三个监听事件
        this.canvasDragAndDrop()
        //监听画布缩放
        this.canvasZoom()
      },
      //设置画布的背景图片
      setBackground(clientWidth, clientHeight) {
        fabric.Image.fromURL(this.canvasBackgroundImage, img => {
          // 缩放背景图片以适应画布
          // 如果背景图片宽度或高度大于画布宽度或高度
          if (img.width > clientWidth || img.height > clientHeight) {
            // 计算缩放比例
            let scaleX = clientWidth / img.width;
            let scaleY = clientHeight / img.height;
            let scale = Math.min(scaleX, scaleY);
            // 缩放背景图片
            img.scaleToWidth(img.width * scale);
            img.scaleToHeight(img.height * scale);
          } else { // 如果背景图片宽度和高度都小于或等于画布宽度和高度
            // 计算背景图片在画布中的位置
            img.left = (clientWidth - img.width) / 2;
            img.top = (clientHeight - img.height) / 2;
          }
          this.canvas.setBackgroundImage(img, this.canvas.renderAll.bind(this.canvas));
        });
      },
      //监听被拖动元素的图片
      handleDragStart(event) {
        this.imageAddress = this.elementList[event].address
      },
      //监听元素是否被下放到画布上
      elementPlacement() {
        this.canvas.on('drop', elt => {
          // 画布元素距离浏览器左侧和顶部的距离
          let offset = {
            left: this.canvas.getSelectionElement().getBoundingClientRect().left,
            top: this.canvas.getSelectionElement().getBoundingClientRect().top
          }
          // 鼠标坐标转换成画布的坐标(未经过缩放和平移的坐标)
          let point = {
            x: elt.e.x - offset.left,
            y: elt.e.y - offset.top,
          }
          // 转换后的坐标,restorePointerVpt 不受视窗变换的影响
          let pointerVpt = this.canvas.restorePointerVpt(point)
          //创建元素
          this.createElement(this.imageAddress, pointerVpt)
        });
      },
      //在画布上生成拖拽过来的元素
      createElement(imageAddress, pointerVpt) {
        fabric.Image.fromURL(imageAddress, oImg => {
          //这个地方做了一下偏移,让鼠标位置为图标的中心,真实的位置信息要还原回去
          oImg.top = pointerVpt.y - 24
          oImg.left = pointerVpt.x - 24
          this.canvas.add(oImg)
        })
      },
      //监听画布缩放
      canvasZoom() {
        this.canvas.on('mouse:wheel', opt => {
          const delta = opt.e.deltaY // 滚轮,向上滚一下是 -100,向下滚一下是 100
          let zoom = this.canvas.getZoom() // 获取画布当前缩放值
          zoom *= 0.999 ** delta
          if (zoom > 20) zoom = 20 // 限制最大缩放级别
          if (zoom < 0.01) zoom = 0.01 // 限制最小缩放级别
          // 以鼠标所在位置为原点缩放
          this.canvas.zoomToPoint({ // 关键点
              x: opt.e.offsetX,
              y: opt.e.offsetY
            },
            zoom // 传入修改后的缩放级别
          )
        })
      },
      // 监听画布拖拽,同时也监听了右键菜单
      canvasDragAndDrop() {
        // 按下鼠标事件
        this.canvas.on("mouse:down", opt => {
          var evt = opt.e
          // 判断:右键,且在元素上右键
          // opt.button: 1-左键;2-中键;3-右键
          // 在画布上点击:opt.target 为 null
          if (opt.button === 3 && opt.target) {
            this.lastMenu = opt.target
            let menu = document.getElementById('menu');
            // 禁止在菜单上的默认右键事件
            menu.oncontextmenu = function(e) {
              e.preventDefault()
            }
            // 显示菜单,设置右键菜单位置
            // 获取菜单组件的宽高
            //这个地方是我自己设置的宽和高计算的,如果你之后复制过去需要修改一下
            const menuWidth = 120
            const menuHeight = menu.childNodes.length * 40
            // 当前鼠标位置
            let pointX = opt.pointer.x
            let pointY = opt.pointer.y
            // 计算菜单出现的位置
            // 如果鼠标靠近画布底部,菜单就出现在鼠标指针上方
            if (this.canvas.height - pointY <= menuHeight) {
              pointY -= menuHeight
            }
            menu.style = `
                  visibility: visible;
                  left: ${pointX}px;
                  top: ${pointY}px;
                  z-index: 100;
                `
            // 将菜单展示
            this.menuDisplay = true
          } else {
            // 将菜单隐藏
            this.menuDisplay = false
          }
          //拖拽
          if (evt.shiftKey === true) {
            this.isDragging = true
            this.canvas.selection = false;
          }
        });
        // 移动鼠标事件
        this.canvas.on("mouse:move", opt => {
          if (this.isDragging && opt && opt.e) {
            var delta = new fabric.Point(opt.e.movementX, opt.e.movementY);
            this.canvas.relativePan(delta);
          }
        });
        // 松开鼠标事件
        this.canvas.on("mouse:up", opt => {
          this.isDragging = false;
          this.canvas.selection = true;
        });
      },
      //删除元素
      deleteElement() {
        this.canvas.remove(this.lastMenu)
        // 将菜单隐藏
        this.menuDisplay = false
      },
      //设定唯一标识
      bindingIdentifier() {
        this.$prompt('请输入唯一编码', '编辑', {
          confirmButtonText: '确定',
          cancelButtonText: '取消'
        }).then(({
          value
        }) => {
          this.$message({
            type: 'success',
            message: '你的唯一编码是: ' + value
          });
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '取消输入'
          });
        });
        // 将菜单隐藏
        this.menuDisplay = false
      }
    }
  }
</script>

<style lang="scss">
  // flex横向布局
  .rowflex {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
  }

  // flex纵向布局
  .columnflex {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }

  // 主div
  .mainDiv {
    height: calc(100vh - 50px) !important;
    width: 100%;
    min-width: 1000px;
  }

  /* 左侧元素区 */
  .leftFiv {
    height: 100%;
    width: 6%;
    min-width: 60px;
    border-right: solid 1px #eee;

    // 元素块
    .element {
      height: 6.5%;
      width: 60%;
      min-height: 48px;
      min-width: 48px;
      margin-top: 20px;

      .element_item {
        height: 90%;
        width: 90%;
      }
    }
  }

  /* 右侧画布区 */
  .rightDiv {
    height: 100%;
    width: 94%;
    min-width: 940px;
  }

  .menu-x {
    z-index: -100;
    position: absolute;
    top: 0;
    left: 0;
    box-sizing: border-box;
    border-radius: 4px;
    box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
    background-color: #fff;
  }

  /* 菜单每个选项 */
  .menu-li {
    box-sizing: border-box;
    padding: 4px 8px;
    border-bottom: 1px solid #ccc;
    cursor: pointer;
    line-height: 30px;
    height: 40px;
    width: 120px;
  }

  /* 鼠标经过的选项,更改背景色 */
  .menu-li:hover {
    background-color: antiquewhite;
  }

  /* 第一个选项,顶部两角是圆角 */
  .menu-li:first-child {
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
  }

  /* 最后一个选项,底部两角是圆角,底部不需要边框 */
  .menu-li:last-child {
    border-bottom: none;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
  }
</style>

一户炊烟煮黄昏文章来源地址https://www.toymoban.com/news/detail-848753.html

到了这里,关于fabric.js 实现元素拖拽、引入图片、标注交互的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Fabric.js 复制粘贴元素

    点赞 + 关注 + 收藏 = 学会了 当你要复制一个 fabric 的元素时,你考虑到的是什么?是深拷贝当前选中对象再添加到画布中? 其实, fabric.js 提供了一个克隆方法,在 fabric.js 官网的案例里也有这个demo:Fabric.js demos · Copy and Paste。 这次就讲讲这个 demo。 动手之前,我们先理清思

    2024年02月08日
    浏览(30)
  • Fabric.js 元素选中状态的事件与样式

    带尬猴! 你是否在使用 Fabric.js 时希望能在选中元素后自定义元素样式或选框(控制角和辅助线)的样式? 如果是的话,可以放心往下读。 本文将手把脚和你一起过一遍 Fabric.js 在对象元素选中后常用的样式设置。 我将对象元素选中后的设置分成3类进行讲解: 控制角 辅助

    2024年02月11日
    浏览(31)
  • 直接在html中引入Vue.js的cdn来实现一个简单的上传图片组件

    当使用 Vue.js 的 CDN 来实现一个简单的上传图片组件时,你可以利用 Vue 的数据绑定和事件处理能力,结合 HTML 和 CSS,轻松地创建一个交互式的图片上传界面。以下是一个示例: index.html TANKING https://afdian.net/item/ffa3292a337c11ee9a8c5254001e7c00

    2024年02月13日
    浏览(30)
  • fabric.js 组件 图片上传裁剪并进行自定义区域标记

    目录 0. 前言 1. 安装fabric与引入 2. fabric组件的使用 3. 属性相关设置 4. 初始化加载 4. 方法 5. 全代码 利用fabric组件,实现图片上传、图片”裁剪“、自定义的区域标记一系列操作 先放一张效果图吧👇 我用的是全局引入方式,视情况调整  先放一个fabric.js API地址☞Api | Fabric中

    2024年01月22日
    浏览(73)
  • 【JS交互篇】事件和元素操作

    事件是用户或者浏览器自身执行的某种动作,如:click、load和mouseover等,都是事件的名字; 原生JavaScipt案例合集 JavaScript +DOM基础 JavaScript 基础到高级 Canvas游戏开发 1.1 窗口或元素相关事件: 事件 描述 onload 页面或图像加载完成后触发的事件 onresize 窗口发生改变时触发的事件

    2024年02月13日
    浏览(26)
  • threeJS 实现加载模型 + 页面按钮交互 + 显示css2Renderer标注

    创建好HTML标注的提示框 创建three的场景等系列 three加载GLTF模型 点击按钮选中模型 + 交互 示例(大概意思就是我点击那个按钮显示那个提示框,模型变材质 + 线) 直接代码 以上就是完整的, 弹窗的位置,和线的结束点是我写死的,因为我不知道创建的弹窗的位置该怎么设置

    2024年02月04日
    浏览(28)
  • vue 实现 dragover拖拽到页面底部时元素自动向下滚动

    公司要求做一个类似于企业微信的日程功能 然后呢 日程组件 需要能拖拽时间段创建 这里 我们使用 dragstart+dragend+dragover 记录被拖动位置完成的 如果没接触过 可以查看我的文章 vue记录鼠标拖拽划过位置并将划过位置变色 这里的话 其实可以在@dragover中做操作 界面上 @dragove

    2024年02月07日
    浏览(32)
  • 微信小程序 — 图片实现缩放,拖拽功能

    movable-view 可移动的视图容器,在页面中可以拖拽滑动。 movable-view必须在 movable-area 组件中,并且必须是直接子节点,否则不能移动。 如果想让图片实现缩放,拖拽效果。则可以把图片放到movable-view容器里面 。 movable-view 可移动的视图容器,在页面中可以拖拽滑动。 效果如下

    2024年02月13日
    浏览(70)
  • Vue 引入高德地图:实现地图展示与交互

    本文将介绍如何在Vue项目中引入高德地图,以及如何实现地图的展示和交互功能。我们将从安装依赖开始,然后配置高德地图的密钥和相关插件,最后演示如何在Vue组件中使用地图组件和实现基本的交互功能。通过本文的指导,您将能够轻松地在Vue项目中集成高德地图,实现

    2024年02月08日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包