Fabric.js使用说明Part 2

news2025/7/24 10:16:22

目录

一、Fabric.js使用说明Part 1

  • Fabric.js简介

  • 开始

  • 方法

  • 事件

  • canvas常用属性

  • 对象属性

  • 图层层级操作

  • 复制和粘贴

二、Fabric.js使用说明Part 2

  • 锁定

  • 拖拽和缩放画布

  • 分组

  • 动画

  • 图像滤镜

  • 渐变

  • 右键菜单删除

三、Fabric.js使用说明Part 3

  • 自由绘画

  • 绘制背景图片

  • 绘制文本

  • 绘制线和路径

一、锁定

Fabric对象可以添加一些属性进行锁定,例如静止水平移动、静止垂直移动,静止缩放等等

1、静止水平移动(lockMovementX)
let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#ffde7d',
  top: 20,
  left: 20
})
rect.lockMovementX = true
canvas.add(rect)
2、静止垂直移动(lockMovementY)
let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#ffde7d',
  top: 20,
  left: 20
})
rect.lockMovementY = true
3、静止旋转(lockRotation)
let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#ff9a3c',
  top: 60,
  left: 160
})
rect.lockRotation = true
4、静止水平缩放(lockScalingX)
let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#ffde7d',
  top: 20,
  left: 20
})
rect.lockScalingX = true
5、静止垂直缩放(lockScalingY)
let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#f95959',
  top: 20,
  left: 20
})
rect.lockScalingY = true
6、限制拖动区域
let boundingBox = new fabric.Rect({
  top: 100,
  left: 100,
  width: 600,
  height: 400,
  fill: '#f95959',
  selectable: false
})
let movingBox = new fabric.Rect({
  top: 150,
  left: 150,
  width: 100,
  height: 100,
  fill: 'yellow',
  hasBorders: false,
  hasControls: false,
  hoverCursor: 'move'
})
this.canvas.add(boundingBox);
this.canvas.add(movingBox);
this.canvas.on("object:moving", (opt) => {
  let top = movingBox.top;
  let left = movingBox.left;
  let topBound = boundingBox.top;
  let bottomBound = topBound + boundingBox.height;
  let leftBound = boundingBox.left;
  let rightBound = leftBound + boundingBox.width;
  opt.target.left = Math.min(Math.max(left, leftBound), rightBound - movingBox.width)
  opt.target.top = Math.min(Math.max(top, topBound), bottomBound - movingBox.height)
})

二、分组

Groups是Fabric最强大的功能之一,它可以将任意数量的Fabric对象组合在一起,形成一个小组,分组后,所有对象都可以一起移动、修改、缩放、旋转甚至更改其外观等

let group = new fabric.Group([circle, text], {
  left: 100,
  top: 100,
  angle: -10
})
canvas.add(group)

修改分组的某个对象的属性:

group.item(0).set("fill","red");
group.item(1).set({
  text:"trololo",
  fill:"white"
})

分组时要记住的另一件事是对象的状态。例如,在与图像组成组时,需要确保这些图像已完全加载:

fabric.Image.fromURL(logo, img => {
  let img1 = img.scale(0.3).set({left: 0, top: 0})

  fabric.Image.fromURL(logo, img => {
    let img2 = img.scale(0.3).set({left: 80, top: 0})

    fabric.Image.fromURL(logo, img => {
      let img3 = img.scale(0.3).set({left: 160, top: 0})
      
      let group = new fabric.Group([img1, img2, img3], {
        left: 10,
        top: 400
      })
      canvas.add(group)
    })
  })
})

三、动画

每个Fabric对象都有一个animate方法,该方法可以动画化该对象,animate(动画属性,动画的结束值,[动画的详细信息])

let rect = new fabric.Rect({
  left: 100,
  top: 100,
  width: 100,
  height: 100,
  fill: 'red'
})
rect.animate("angle", 45, {
  onChange: canvas.renderAll.bind(canvas)
})
canvas.add(rect)

第一个参数是要设置动画的属性。第二个参数是动画的结束值。如果矩形具有-15°的角度,并且我们传递了45,则动画将从-15°变为45°。第三个参数是一个可选对象,用于指定动画的详细信息-持续时间,回调,缓动等

rect.animate("angle", 45, {
  from: 0, // 允许指定可设置动画的属性的起始值(如果我们不希望使用当前值)
  duration: 1000, // 默认为500(ms),可用于更改动画的持续时间
  easing: fabric.util.ease.easeOutBounce, // 缓动功能 easeOutBounce、easeInCubic、easeOutCubic、easeInElastic、easeOutElastic、easeInBounce、easeOutExpo
  onChange: canvas.renderAll.bind(canvas), // 在每次刷新时都会执行
  onComplete: (e) => { console.log(e) } // 在动画结束时调用的回调
})

animate的一个方便之处在于它还支持相对值

// 向右移动100px
rect.animate('left', '+=100', {
  onChange: canvas.renderAll.bind(canvas)
})

// 逆时针旋转5度
rect.animate('angle', '-=5', {
  onChange: canvas.renderAll.bind(canvas)
})

四、图像滤镜

fabric.Image的每个实例都具有“ filters”属性,该属性是一个简单的过滤器数组。该阵列中的每个过滤器都是Fabric过滤器之一的实例。或您自己的自定义过滤器的实例。

let url = 'http://localhost:82/public/img/5.png' // 图片url
let base64Url = await this.imgUrlToBase64(url) // 图片base64 url
// 正常照片
fabric.Image.fromURL(url, img => {
  img.scale(0.3)
  canvas.add(img)
})

// 单个滤镜
fabric.Image.fromURL(base64Url, img => {
  img.scale(0.3)
  img.left = 300
  // 添加滤镜
  img.filters.push(new fabric.Image.filters.Grayscale())
  // 图片加载完成之后,应用滤镜效果
  img.applyFilters()
  canvas.add(img)
})

// 叠加滤镜
fabric.Image.fromURL(base64Url, img => {
  img.scale(0.3)
  img.set({
    left: 300,
    top: 250,
  })
  img.filters.push(
    new fabric.Image.filters.Grayscale(),
    new fabric.Image.filters.Sepia(), //色偏
    new fabric.Image.filters.Brightness({ brightness: 0.2 }) //亮度
  )
  img.applyFilters()
  canvas.add(img)
})

(说明:这里图片可能会出错,放本地图片地址会报“Cannot read property 'naturalWidth' of null”的错误,直接放网络图片地址会报“Failed to execute 'texImage2D' on 'WebGLRenderingContext': The image element contains cross-origin data, and may not be loaded.”的错误。解决方法就是将转为Base64格式

五、渐变

Fabric支持在所有对象上设置填充或描边属性的渐变,首先创建渐变,然后将其分配给填充或描边。

1、线性渐变
// 圆
let circle = new fabric.Circle({
  left: 100,
  top: 100,
  radius: 50,
})
let gradient = new fabric.Gradient({
  type: 'linear', // linear or radial
  gradientUnits: 'pixels', // pixels or pencentage 像素 或者 百分比
  coords: { x1: 0, y1: 0, x2: circle1.width, y2: 0 }, // 至少2个坐标对(x1,y1和x2,y2)将定义渐变在对象上的扩展方式
  colorStops:[ // 定义渐变颜色的数组
    { offset: 0, color: 'red' },
    { offset: 0.2, color: 'orange' },
    { offset: 0.4, color: 'yellow' },
    { offset: 0.6, color: 'green' },
    { offset: 0.8, color: 'blue' },
    { offset: 1, color: 'purple' },
  ]
})
circle.set('fill', gradient);
canvas.add(circle)
2、径向渐变
let circle = new fabric.Circle({
  left: 100,
  top: 100,
  radius: 50
})
let gradient = new fabric.Gradient({
  type: 'radial',
  coords: {
    r1: 50,
    r2: 0,
    x1: 50,
    y1: 50,
    x2: 50,
    y2: 50,
  },
  colorStops: [
    { offset: 0, color: '#fee140' },
    { offset: 1, color: '#fa709a' }
  ]
})
circle.set('fill', gradient);
canvas.add(circle)

六、拖拽和缩放画布

1、拖拽画布
<script>
  export default {
    data() {
      return {
        lastPosX: 0,       // 上次鼠标位置X坐标
        lastPosY: 0,       // 上次鼠标位置Y坐标
        isDragging: false, // 是否可以拖拽画布
      }
    },
    mounted() {
      ... // 初始化canvas
      this.canvas.on('mouse:down', this.onMouseDown)
      this.canvas.on('mouse:move', this.onMouseMove)
      this.canvas.on('mouse:up', this.onMouseUp)
    },
    methods: {
      // 监听鼠标按下事件
      onMouseDown(opt) {
        this.lastPosX = opt.e.clientX
        this.lastPosY = opt.e.clientY
        this.isDragging = true
      },
      // 监听鼠标移动事件
      onMouseMove(opt) {
        if (this.isDragging) {
          this.canvas.viewportTransform[4] += opt.e.clientX - this.lastPosX
          this.canvas.viewportTransform[5] += opt.e.clientY - this.lastPosY
          this.canvas.requestRenderAll()
          this.lastPosX = opt.e.clientX
          this.lastPosY = opt.e.clientY
        }
      },
      // 监听鼠标松开事件
      onMouseUp(opt) {
        if (this.isDragging) {
          this.canvas.setViewportTransform(this.canvas.viewportTransform)
          this.isDragging = false
        }
      }
    }
  }
</script>
2、以画布中心点为基准手动缩放
<template>
  <el-tooltip content="放大" placement="bottom-start">
    <span class="iconfont icon-fangda" @click="onManualScale(-100)"></span>
  </el-tooltip>
 <el-tooltip content="缩小" placement="bottom-start">
    <span class="iconfont icon-suoxiao" @click="onManualScale(100)"></span>
  </el-tooltip>
</template>
<script>
export default {
  // 中心点缩放画布
  onManualScale(delta) {
    let zoom = canvas.getZoom() // 获取画布当前缩放值
    zoom *= 0.999 ** delta
    zoom = zoom > 10 ? 10 : (zoom < 0.1 ? 0.1 : zoom) // 最大放大10倍,最小缩小至10%
    canvas.zoomToPoint({ // 以画布中心点为基准缩放
      x: this.canvasBoxWidth / 2,  // canvasBoxWidth 画布宽度
      y: this.canvasBoxHeight / 2  // canvasBoxHeight 画布高度
    }, zoom)
  }
}
</script>
3、以鼠标指针位置为基准缩放
this.canvas.on('mouse:wheel', this.onMouseWheel)

// 监听鼠标放大缩小事件
onMouseWheel(opt) {
  let delta = opt.e.deltaY // 滚轮,向上滚一下是 -100,向下滚一下是 100
  let zoom = this.canvas.getZoom() // 获取画布当前缩放值
  zoom *= 0.999 ** delta
  zoom = zoom > 10 ? 10 : (zoom < 0.1 ? 0.1 : zoom) // 最大放大10倍,最小缩小至10%
  this.canvas.zoomToPoint({ // 以鼠标指针位置为基准缩放
    x: opt.e.offsetX,
    y: opt.e.offsetY
  }, zoom)
  opt.e.preventDefault()
  opt.e.stopPropagation()
}

七、右键菜单删除

<template>
  <div class="canvas-box" ref="canvasBox">
    <canvas id="canvas"></canvas>
    <div id="delMenu" ref="delMenu" v-show="isShowDelMenu" :style="delMenuStyle" @contextmenu.prevent="">
      <el-button type="iconButton" icon="h-icon-delete" @click="handleDeleteMenu">删除</el-button>
    </div>
  </div>
</template>
<script>
export default {
  name: 'PointerDetail',
  data () {
    return {
      canvas: null,
      activeEle: null, // 上次选中元素
      isShowDelMenu: false, // 是否显示删除弹窗
      delMenuStyle: '' // 删除弹窗定位样式
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    // 初始化
    init() {
      this.canvas = new fabric.Canvas('canvas', {
        fireRightClick: true, // 启用右键,button的数字为3
        stopContextMenu: true, // 禁止默认右键菜单
      })
      this.canvas.on('mouse:down', this.onMouseDown)
    },
    // 监听鼠标按下事件
    onMouseDown(opt) {
      // 还原上次选中状态
      if (this.activeEle) {
        this.activeEle.set('fill', 'transparent')
        this.canvas.renderAll()
      }
      this.activeEle = opt.target || null
      // 按下鼠标右键
      if (opt.button === 3) {
        // 点击到非图片控件 显示删除弹窗和填充控件背景色
        if (opt.target && opt.target.type !== 'image') {
          this.activeEle.set('fill', 'rgba(100, 100, 255, 0.3)')
          this.canvas.renderAll()
          this.isShowDelMenu = true
          this.$nextTick(() => {
            this.delMenuStyle = this.getMenuStyle(this.$refs.delMenu, opt)
          })
        } else {
          // 否则隐藏删除弹窗
          this.hiddenMenu()
        }
      // 按下鼠标左键
      } else {
        this.hiddenMenu()
      }
    },
    // 获取弹窗坐标
    getMenuStyle(ele, opt) {
      let menuWidth = ele.offsetWidth
      let menuHeight = ele.offsetHeight
      let pointX = opt.pointer.x + 2
      let pointY = opt.pointer.y + 2
      if (this.$refs.canvasBox.offsetWidth - pointX <= menuWidth) {
        pointX -= menuWidth
      }
      if (this.$refs.canvasBox.offsetHeight - pointY <= menuHeight) {
        pointY -= menuHeight
      }
      return `left: ${pointX}px; top: ${pointY}px;`
    },
    // 隐藏菜单
    hiddenMenu() {
      this.activeEle = null
      this.isShowDelMenu = false
    },
    // 删除选中元素
    handleDeleteMenu() {
      this.canvas.remove(this.activeEle)
      this.canvas.requestRenderAll()
      this.hiddenMenu()
    }
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/368388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

传统豪华品牌引领?智能座舱进入「沉浸式娱乐体验」新周期

智能座舱正在进入硬件定型、软件&#xff08;功能&#xff09;升级以及多应用融合的新周期。 高工智能汽车研究院监测数据显示&#xff0c;2022年中国市场&#xff08;不含进出口&#xff09;乘用车搭载智能数字座舱&#xff08;大屏语音车联网OTA&#xff09;前装标配交付795…

【死磕数据库专栏启动】在CentOS7中安装 MySQL5.7版本实战

文章目录前言实验环境一. 安装MySQL1.1 配置yum源1.2 安装之前的环境检查1.3 下载MySQL的包1.4 开始使用yum安装1.5 启动并测试二. 设置新密码并重新启动2.1 设置新密码2.2 重新登录测试总结前言 学习MySQL是一件比较枯燥的事情&#xff0c;学习开始之前要先安装MySQL数据库&a…

【Linux修炼】14.磁盘结构/文件系统/软硬链接/动静态库

每一个不曾起舞的日子&#xff0c;都是对生命的辜负。 磁盘结构/文件系统/软硬链接/动静态库前言一.磁盘结构1.1 磁盘的物理结构1.2 磁盘的存储结构1.3 磁盘的逻辑结构二.理解文件系统2.1 对IO单位的优化2.2 磁盘分区与分组2.3 分组的管理方法2.4 文件操作三.软硬链接3.1理解硬…

测试4年裸辞失业,面试17k的测试岗被按在地上摩擦,结局让我崩溃大哭...

作为IT行业的大热岗位——软件测试&#xff0c;只要你付出了&#xff0c;就会有回报。说它作为IT热门岗位之一是完全不虚的。可能很多人回说软件测试是吃青春饭的&#xff0c;但放眼望去&#xff0c;哪个工作不是这样的呢&#xff1f;会有哪家公司愿意养一些闲人呢&#xff1f;…

「smardaten」上架钉钉应用中心!让进步再一次发生

使用钉钉的团队小伙伴们&#xff0c;smardaten给您送来福利啦~为了给更多团队提供更优质的应用开发体验&#xff0c;方便用户在线、快速使用无代码&#xff0c;数睿数据近期在【钉钉应用中心】发布smardaten在线版本。继与华为云、亚马逊云建立战略合作之后&#xff0c;smardat…

微信小程序实现分享到朋友圈的功能

分享朋友圈官方API&#xff1a;分享到朋友圈 1、分享到朋友圈接口设置事项 2、onShareTimeline()注意事项 3、分享朋友圈后&#xff0c;测试发现&#xff0c;没有数据请求。 用户在朋友圈打开分享的小程序页面&#xff0c;并不会真正打开小程序&#xff0c;而是进入一个“小程…

浏览器缓存策略

先走强缓存&#xff0c;再走协商缓存 强缓存 不发送请求&#xff0c;直接使用缓存的内容 状态码200 当前会话没有关闭的话就是走memory cache&#xff0c;否则就是disk cache 由响应头的 Pragma&#xff08;逐渐废弃&#xff0c;优先级最高&#xff09;&#xff0c;catch-…

LeetCode 817. 链表组件

LeetCode 817. 链表组件 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 给定链表头结点 headheadhead&#xff0c;该链表上的每个结点都有一个 唯一的整型值 。同时给定列表 numsnumsnums&#xff0c;该列表是上述链表中整型值的一个子集。 返回列表 numsnu…

自动驾驶仿真:ECU TEST 、VTD、VERISTAND连接配置

文章目录一、ECU TEST 连接配置简介二、TBC配置 test bench configuration三、TCF配置 test configuration提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、ECU TEST 连接配置简介 1、ECU TEST&#xff08;简称ET&#xff09;&#xff0c;用于HIL仿…

MySQL tinyint(1) 、int(32) 与 varchar(255) 长度含义不同

MySQL tinyint(1) 、int(32) 与 varchar(255) 长度含义不同 发现 tinyint(1)&#xff0c;int(32) 和 varchar(255) 这里面的数字的含义是不同的。 先说数字类型 tinyint 和 int 等 他们能存储的字节大小是与类型绑定的&#xff0c;即定义了 tinyint 或者 int 就确定了能存储…

【C++的OpenCV】第六课-OpenCV图像常用操作(三):OpenCV的图像的侵蚀和扩张

让我们继续一、图像的侵蚀和扩张1.1 侵蚀1.1.1 函数原型1.1.2 侵蚀的效果1.1.3 关于侵蚀的解释1.2 扩张1.2.1 函数原型1.2.2 扩张的效果二、实例一、图像的侵蚀和扩张 本章节中我们将会学习到&#xff1a; cv::erode() 函数详情cv::dilate() 函数详情 两个函数的基本使用方法…

java 接口 详解

目录 一、概述 1.介绍 : 2.定义 : 二、特点 1.接口成员变量的特点 : 2.接口成员方法的特点 : 3.接口构造方法的特点 : 4.接口创建对象的特点 : 5.接口继承关系的特点 : 三、应用 : 1.情景 : 2.多态 : ①多态的传递性 : ②关于接口的多态参数和多态…

Android ION 相关信息查看方法

目录 查看DMA buffer 信息 查看ion buffer 的总体分配情况 分配的ION buffer 都会设置为DMA buffer&#xff0c;以fd的形式交给使用方&#xff0c; 如app, camera等。 查看ion 的使用情况&#xff0c;可以查看ion 的各个heap分配情况&#xff0c; 也可以从DMA buffer 入手查…

勒索软件BlackByte出现新变种,系Go语言编写

BlackByte 是一个提供勒索软件即服务&#xff08;RaaS&#xff09;的攻击组织&#xff0c;自从 2021 年 7 月以来一直保持活跃。最初&#xff0c;BlackByte 使用 C# 开发&#xff0c;最近攻击者使用 Go 重写了恶意软件。FBI 也已经发布公告披露 BlackByte 已经攻击了许多公司&a…

文献阅读 An implementation of the seismic resolution enhancing network based on GAN

题目 An implementation of the seismic resolution enhancing network based on GAN 基于GAN的地震分辨率增强网络的实现 摘要 对于地震数据&#xff0c;本文利用深度学习来学习不同层次的特征并将它们合并以恢复缺失的分辨率。 将GAN网络引入到地震数据处理&#xff1b;对…

劳保防护用品穿戴检测 python

劳保防护用品穿戴检测算法通过pythonOpencv深度学习技术&#xff0c;劳保防护用品穿戴检测算法对现场人员防护穿戴用品进行全天候检测&#xff0c;当检测到未按照要求进行穿戴&#xff0c;立即对现场违规人员进行抓拍。Python是一种由Guido van Rossum开发的通用编程语言&#…

在做自动化测试前需要知道的

什么是自动化测试&#xff1f; 做测试好几年了&#xff0c;真正学习和实践自动化测试一年&#xff0c;自我感觉这一个年中收获许多。一直想动笔写一篇文章分享自动化测试实践中的一些经验。终于决定花点时间来做这件事儿。 首先理清自动化测试的概念&#xff0c;广义上来讲&a…

2023年DAMA-CDGA/CDGP数据治理工程师认证报名时间

2023年DAMA-CDGA/CDGP数据治理工程师认证报名时间 DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决…

企业急需:拥有一个属于自身的知识库!

如今&#xff0c;拥有知识库对任何企业来说都是绝对必要的。特别是在软件即服务方面。如果您真的希望您的 SaaS 业务取得成功&#xff0c;您需要从第一天开始构建知识库。为什么&#xff1f;首先&#xff0c;SaaS 公司有一个货币化模型&#xff0c;专注于他们的每月经常性收入 …

plsql过程语言之uxdb与oracle语法差异

序号场景uxdboracle1在存储过程中使用goto子句create or replace procedure uxdbc_oracle_extension_plsql_goto_0001_procedure01(t1 int) language plsql as $$ begin if t1%20 then goto even_number; else goto odd_number; end if; <<even_number>> raise…