使用fabric.js实现对图片涂鸦、文字编辑、平移缩放与保存功能

文章目录

    • 背景
    • 1.初始化画布
      • 1.创建画布
      • 2.设置画布大小
    • 2.渲染图片
    • 3.功能:开启涂鸦
    • 4.功能:添加文字
    • 5.旋转图片
    • 6.画布平移
    • 7.画布缩放
    • 8.保存图片
    • 9.上传图片
    • 10.销毁实例
    • 11.总结

背景

项目中有个需求,需要对图片附件进行简单的编辑操作,如涂鸦、添加文字、拖动与缩放图片、旋转图片、保存图片、上传图片等。经过技术选型对比,决定使用fabric.js开源库。

以下的代码都为简化版。

1.初始化画布

图片需要绘制在canvas画布上进行相关的编辑操作。

1.创建画布

<canvas id="editorcanvas" />

<script>
import { fabric } from "fabric";
export default {
	mounted() {
		this.canvas = new fabric.Canvas("editorcanvas", {
          selection: false, // 不允许从画板框选,但允许选中元素
          centeredRotation: true, // true时Canvas上的所有对象使用中间点(而不是默认的左上角)作为旋转的原点
          // backgroundVpt: false, // 锁定背景图,不受画板缩放移动的影响
          // isDrawingMode: true, // 开启自由绘制
          // selectionFullyContained: true, // 只选择完全包含在拖动选择矩形中的元素
        });
        this.canvas.freeDrawingBrush.width = 4; // 画笔的宽度
        this.canvas.freeDrawingBrush.limitedToCanvasSize = true; // 自由绘制被限制为画布大小
	},
	
}
</sxript>

2.设置画布大小

由于每个图片的宽高都是不定的,可能是横图也可能是纵图。要根据图片的宽高来动态设置画布的宽高,保证图片在画布中是完全铺满的,并且画布的大小需适应屏幕。

这里还需要注意图片的跨域问题

const img = document.createElement("img");
img.crossOrigin = "anonymous";
img.src = this.file.playUrl; // 图片的url
img.onload = () => {
  let width;
  let height;
  const radio = img.width / img.height;
  if (radio > 1) {
    width = Math.min(img.width, 1200);
    height = width / radio;
  } else {
    height = Math.min(img.height, 700);
    width = height * radio;
  }
  this.domData.imgWidth = img.width;
  this.domData.imgHeight = img.height;
  this.domData.width = width;
  this.domData.originWidth = width;
  this.domData.height = height;
  this.domData.originHeight = height;
  this.initCanvas(width, height, img.width, img.height, {
    scaleWidth: width,
    scaleHeight: height,
  });
};

initCanvas(width, height, imgWidth, imgHeight, info) {
  this.canvas.setDimensions({ width, height }); // 设置画布的宽高
},

2.渲染图片

图片以背景图的形式渲染在画布上。

initCanvas(width, height, imgWidth, imgHeight, info) {
  this.canvas.setDimensions({ width, height }); // 设置画布的宽高
  this.$nextTick(() => {
    this.canvas.setBackgroundImage(
      this.file.playUrl,
      this.canvas.renderAll.bind(this.canvas),
      {
        imgWidth,
        imgHeight,
        scaleX: info.scaleWidth / imgWidth,
        scaleY: info.scaleHeight / imgHeight,
        left: width / 2,
        top: height / 2,
        angle: this.rotateValue, // 旋转角度,默认为0
        originX: "center",
        originY: "center",
        crossOrigin: "anonymous",
      }
    );
  });
},

3.功能:开启涂鸦

在开启涂鸦、添加文字等功能时,请自行注意功能的互斥。

涂鸦就是开启自由绘制功能。

this.canvas.freeDrawingBrush.width = Number(this.lineWidthValue || 4)
this.canvas.freeDrawingBrush.color = this.colorDrawValue;
this.canvas.isDrawingMode = true; // 自由绘制

4.功能:添加文字

实现思路:在画布中间添加一行文本,并且让文本处于活跃状态,并选中所有文本,方便用户直接修改文字。

const text = new fabric.IText("请输入文本", {
  fill: this.colorTextValue,
});
text.setControlsVisibility({ // 控制文本的手柄
  mt: false,
  mr: false,
  mb: false,
  ml: false,
});
this.canvas.add(text);
this.canvas.viewportCenterObject(text); // 画布中间
this.canvas.setActiveObject(text); // 活跃状态
text.enterEditing(); // 进入编辑状态
text.selectAll(); // 选中所有文本

5.旋转图片

思路就是改变画布大小,让画布的宽高进行互换,并且重新渲染图片背景,此时渲染的图片是有旋转角度 rotateValue 的。

这里有个注意点,我这种实现方式在旋转后会清空之前的所有绘制,不清空的话之前的绘制会有坐标偏移,展示不对。

revolveCanvas() {
  const { imgWidth, imgHeight, originWidth, originHeight } = this.domData;
  if (!this.rotateValue || !((this.rotateValue / 90) % 2)) {
    this.domData.width = originHeight;
    this.domData.height = originWidth;
  } else {
    this.domData.width = originWidth;
    this.domData.height = originHeight;
  }
  this.rotateValue += 90; // 累加,顺时针旋转
  this.canvas.clear(); // 清空之前画布上的所有绘制
  this.activeThingchange(null);
  this.isActive = null;
  this.initCanvas(
    this.domData.width,
    this.domData.height,
    imgWidth,
    imgHeight,
    {
      scaleWidth: this.domData.originWidth,
      scaleHeight: this.domData.originHeight,
    }
  );
},

6.画布平移

this.canvas.on("mouse:down", (opt) => {
  const evt = opt.e;
  this.dragging.open = true;
  this.dragging.lastPosX = evt.clientX;
  this.dragging.lastPosY = evt.clientY;
});
this.canvas.on("mouse:move", (opt) => {
  if (this.dragging.open) {
    const evt = opt.e;
    const vpt = this.canvas.viewportTransform;
    vpt[4] += evt.clientX - this.dragging.lastPosX;
    vpt[5] += evt.clientY - this.dragging.lastPosY;
    this.canvas.requestRenderAll(); // 异步更新画板,提升性能
    this.dragging.lastPosX = evt.clientX;
    this.dragging.lastPosY = evt.clientY;
  }
});
this.canvas.on("mouse:up", (e) => {
  if (this.dragging.open) {
    this.canvas.setViewportTransform(this.canvas.viewportTransform);
    this.dragging.open = false;
  }
});

7.画布缩放

有两种画布缩放方式,第一种是以鼠标指针为中心点来缩放画布,第二种是以画布的原点为中心点来缩放画布。

this.canvas.on("mouse:wheel", (opt) => {
  const delta = opt.e.deltaY; // 正值为放大
  let zoom = this.canvas.getZoom();
  zoom *= 0.999 ** delta;
  if (zoom > 20) zoom = 20;
  if (zoom < 1) zoom = 1;
  this.canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom); // 以鼠标指针来缩放画板
  // this.canvas.setZoom(zoom) // 以画布原点来缩放画板
});

8.保存图片

保存编辑后的图片,这里有个要求,就是在保存图片时,图片不能失真。

因为如果是一个高像素比的图片,绘制在画布上时图片会进行压缩,如果直接使用canvastoDataURL方式获取编辑后图片的base64格式url,图片会失真。

自己想的一个思路是:①点击保存图片按钮时,整个页面增加一个v-loading效果,②将画布的宽高改为原图片的宽高大小,进行1:1还原,③重新绘制背景图,④重绘完成后获取到编辑后图片的url,走保存逻辑,同时将画布状态还原为点击保存图片之前的状态,⑤最后取消v-loading的效果。

saveToLocal() {
  const { imgWidth, imgHeight, width, height, initWidth, initHeight } =
    this.commonSaveUtil();
  setTimeout(() => {
    const dataURL = this.canvas.toDataURL({
      format: "jpeg",
      quality: 1,
      width: initWidth,
      height: initHeight,
    });
    this.canvas.backgroundVpt = true;
    this.canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
    this.initCanvas(width, height, imgWidth, imgHeight, {
      scaleWidth: this.domData.originWidth,
      scaleHeight: this.domData.originHeight,
    });
    const link = document.createElement("a");
    link.download = new Date().getTime();
    link.href = dataURL;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    this.loading = false;
  }, 1500);
},

commonSaveUtil() {
  this.loading = true;
  this.canvas.backgroundVpt = false;
  const { imgWidth, imgHeight, width, height } = this.domData;
  let initWidth;
  let initHeight;
  if (!this.rotateValue || !((this.rotateValue / 90) % 2)) {
    initWidth = imgWidth;
    initHeight = imgHeight;
  } else {
    initWidth = imgHeight;
    initHeight = imgWidth;
  }
  this.initCanvas(initWidth, initHeight, imgWidth, imgHeight, {
    scaleWidth: imgWidth,
    scaleHeight: imgHeight,
  });
  this.canvas.viewportTransform = [
    initWidth / width,
    0,
    0,
    initHeight / height,
    0,
    0,
  ];
  return { imgWidth, imgHeight, width, height, initWidth, initHeight };
},

9.上传图片

逻辑与保存图片类似,只是需要将获取到的base64格式的url转为file类型,再上传给服务器。

function dataURLtoFile(dataurl, filename) {
  // base64 -> file
  const arr = dataurl.split(",");
  const mime = arr[0].match(/:(.*?);/)[1];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, { type: mime });
}

const file = dataURLtoFile(dataURL, new Date().getTime());

10.销毁实例

我是把图片编辑功能封装成了一个组件,可以在项目的多个地方使用。在进行组件销毁时,建议手动把实例销毁掉。
在这里插入图片描述

11.总结

使用fabric.js库实现这些功能比较简单,网上有很多博客可供参考,这里贴一个开发时经常查阅的中文文档:http://funcion_woqu.gitee.io/fabric-doc/api/#basebrush

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言注意点(4)

1、void *a是什么意思 答&#xff1a;泛型指针&#xff0c;但不规定其类型(就是地址确定&#xff0c;但数据长度不确定)在动态分配内存时&#xff0c;malloc的返回值就是该类型&#xff0c;方便用户进行强制转换。 2、VS怎么一键规范格式 for(i0;i<10;i)enter后&#xff0c;…

在C++11中利用for()循环遍历迭代器的同时,也可对容器内的数据进行更改

一、for (auto &&it : _groups){}含义&#xff1a; for (auto &&it : _groups) 是一个范围-based for 循环&#xff08;也称为 foreach 循环&#xff09;&#xff0c;用于遍历容器 _groups 中的元素。这种循环语法在 C11 及更高版本中引入&#xff0c;允许以一…

自定义列表里面实现多选功能

需求 我们在开发过程中有时候会遇到列表里面会有多选&#xff0c;然后列表样式也要进行自定义。这里我们如果直接使用ElementUI组件el-table表格的时候这里实现起来可能比较复杂不方便&#xff0c;我们这里手写自定义一下列表里面多选的功能。 实现效果如下图所示&#xff1a…

私域和微商有什么区别?

私域和微商到底有什么区别呢&#xff1f;其实这两个东西有着本质性区别。 私域&#xff1a; 通过原有商业或者新媒体方式获取粉丝或顾客&#xff0c;然后用微信等社交工具&#xff0c;多方位展现&#xff0c;人格专业。 最终目标是让粉丝或顾客成为品牌或IP的朋友&#xff0…

【嵌入式】About USB Powering

https://www.embedded.com/usb-type-c-and-power-delivery-101-power-delivery-protocol/https://www.embedded.com/usb-type-c-and-power-delivery-101-power-delivery-protocol/ Type-C接口有多强&#xff1f;PD协议又是什么&#xff1f;-电子发烧友网由于Type-C接口自身的强…

STM32入门教程-2023版【3-2】详细讲解实现LED流水灯

关注 点赞 不错过精彩内容 大家好&#xff0c;我是硬核王同学&#xff0c;最近在做免费的嵌入式知识分享&#xff0c;帮助对嵌入式感兴趣的同学学习嵌入式、做项目、找工作! 三、LED流水灯 依据电路图连接电路 复制LED闪烁的工程&#xff0c;改个名字叫3-2 LED流水灯 修改…

Android 内容生成pdf文件

1.引入itext7 implementation com.itextpdf:itext7-core:7.1.13上面比较大&#xff0c;可以直接下载需要集成的jar包 implementation files(libs\\layout-7.1.13.jar) implementation files(libs\\kernel-7.1.13.jar) implementation files(libs\\io-7.1.13.jar) implementatio…

亚马逊站内广告位置在哪设置?怎么设置广告位置?-站斧浏览器

亚马逊站内广告位置在哪设置&#xff1f; 亚马逊提供了多种广告类型&#xff0c;包括&#xff1a; Sponsored Products&#xff08;赞助产品&#xff09;&#xff1a;在搜索结果和商品详情页中展示。 Sponsored Brands&#xff08;赞助品牌&#xff09;&#xff1a;在搜索结…

C语言快速入门——前景引入

计算机语言 计算机语言发展计算机的世界操作系统概述计算机编程语言C语言开发环境部署 各位小伙伴想要博客相关资料的话关注公众号&#xff1a;chuanyeTry即可领取相关资料&#xff01; 文章来自&#xff1a;https://www.itbaima.cn/document 计算机语言发展 在学习C语言之前&…

进程的介绍及相关命令

首先&#xff0c;先了解一下计算机五大性能的命令 cpu top w 内存 top free 硬盘剩余 df 硬盘读写性能 iostat 网络带宽 iftop 一&#xff0c;进程与程序 1&#xff0c;什么是程序 &#xff1a; 硬盘上躺着&#xff0c;执行特点任务的一串代码 2&am…

VS2010 ,创建DLL,并调用DLL

一、创建DLL 1. 新建Win32空项目 项目命名为genxls。 2. 创建DLL空项目 3. 头文件&#xff0c;新建项&#xff0c; genxls.h 头文件内容为 // genxls.h #ifndef _DLL_API #define _DLL_API _declspec(dllexport) #else #define _DLL_API _declspec(dllimport) #endif _DLL_A…

我不想学JAVA---------JAVA和C的区别

前言 我一个研究方向是SLAM的为什么要来学JAVA。 从九月份开学到现在&#xff0c;已经学了Linux&#xff0c;数据结构&#xff0c;SLAM&#xff0c;C的基础操作&#xff0c;期间还参与编写了一本VHDL的教材。还有上课、考试什么的其他杂七杂八的事情就不说了。 读研好苦逼&…

IPv6邻居发现协议(NDP)---路由发现

IPv6路由发现(前缀公告) 邻居发现 邻居发现协议NDP(Neighbor Discovery Protocol)是IPv6协议体系中一个重要的基础协议。邻居发现协议替代了IPv4的ARP(Address Resolution Protocol)和ICMP路由器发现(Router Discovery),它定义了使用ICMPv6报文实现地址解析,跟踪邻…

利用蚁剑钓鱼上线CS

前言 ​ 中国蚁剑使用Electron构建客户端软件&#xff0c;Electron实现上用的是Node.js&#xff0c;并且Node.js能执行系统命令&#xff0c;故可以利用蚁剑的webshell页面嵌入js来直接执行命令&#xff0c;进而钓鱼来上线CS。&#xff08;类似Goby&#xff0c;Goby也是使用Ele…

小迪安全第一天

一、常用的专业术语 参考&#xff1a; https://www.cnblogs.com/sunny11/p/13583083.html POC&#xff08;proof of concept&#xff09;验证漏洞的代码EXP(exploit) 利用对一段漏洞代码的利用Playload:有效载荷&#xff0c;当利用exploit成功后如何通过一个管道把这个权限给…

制药企业符合CSV验证需要注意什么?

在制药行业中&#xff0c;计算机化系统验证&#xff08;CSV&#xff09;是确保生产过程的合规性和数据完整性的关键要素。通过CSV验证&#xff0c;制药企业可以保证其计算机化系统的可靠性和合规性&#xff0c;从而确保产品质量和患者安全。然而&#xff0c;符合CSV验证并不是一…

【通讯录案例-延时调用-第三方框架-HUD框架 Objective-C语言】

一、我们接着来看这个通讯录啊,上节课我们说到这里, 1.给它加个注释,// 当用户名和密码正确的时候 进行跳转 我们现在已经把这个判断用户名和密码,登录的这个,以及Segue的简单使用,我们已经说完了, 好,然后呢,在这个里边儿啊, 我们呢,示例程序里边儿,是这个样子的…

C#自动删除20天前文件夹图片

资料夹如下&#xff0c;需求为自动删除20天前保存的图片 如下为该方法函数&#xff0c;保留天数可以自定义 public static void CleanFile(){string path $"{SvMaster.DataPath}\\Image";\\文件夹路径DirectoryInfo dir new DirectoryInfo(path);FileSystemInfo[] …

CEC2017(Python):七种算法(RFO、DBO、HHO、SSA、DE、GWO、OOA)求解CEC2017

一、7种算法简介 1、红狐优化算法RFO 2、蜣螂优化算法DBO 3、哈里斯鹰优化算法HHO 4、麻雀搜索算法SSA 5、差分进化算法DE 6、灰狼优化算法GWO 7、鱼鹰优化算法OOA 二、CEC2017简介 参考文献&#xff1a; [1]Awad, N. H., Ali, M. Z., Liang, J. J., Qu, B. Y., &…

亚马逊促销效果不好怎么办?亚马逊促销规则是什么?-站斧浏览器

亚马逊促销效果不好怎么办&#xff1f; 分析原因&#xff1a;首先需要深入分析促销效果不佳的原因。可能是促销活动的设计不够吸引人&#xff0c;或者是目标受众定位不准确。 调整策略&#xff1a;根据分析结果调整促销策略。例如&#xff0c;优化广告文案、更改推广时段或调…