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

news2024/11/26 10:33:25

文章目录

    • 背景
    • 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

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

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

相关文章

网络故障问题一般性检查排查思路

一、基本连通性检查 在网络中ping是一个十分强大的TCP/IP工具。它可以用来检测网络的连通情况和分析网络速度、也可以ping网址根据域名得到服务器IP、同时我们根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量。 ping 网址&#xff0c;有几种输出情况&a…

【OpenCV】在MacOS上使用OpenCvSharp

前言 OpenCV是一个基于Apache2.0许可&#xff08;开源&#xff09;发行的跨平台计算机视觉和机器学习软件库&#xff0c;它具有C&#xff0c;Python&#xff0c;Java和MATLAB接口&#xff0c;并支持Windows&#xff0c;Linux&#xff0c;Android和Mac OS。OpenCvSharp是一个Op…

Flume基础知识(七):Flume 事务与 Flume Agent 内部原理

1. Flume 事务详解 2. Flume Agent 内部原理 重要组件&#xff1a; 1&#xff09;ChannelSelector ChannelSelector 的作用就是选出 Event 将要被发往哪个 Channel。其共有两种类型&#xff0c; 分别是 Replicating&#xff08;复制&#xff09;和 Multiplexing&#xff08;多…

如何计算非线性负载的功率需求?

非线性负载的功率需求计算是一个相对复杂的过程&#xff0c;因为非线性负载的电流和电压之间的关系不是简单的正比关系。在计算非线性负载的功率需求时&#xff0c;需要考虑负载的特性、工作状态以及电源电压等因素。 确定负载的类型&#xff1a;首先需要了解负载的具体类型&am…

MS713/MS713T:CMOS 低压、4Ω四路单刀单掷开关,替代ADG713

产品简述 MS713/MS713T 是一款单芯片 CMOS 4 路可选择开关&#xff0c;具有低 功耗、高开关速度、低导通阻抗、低漏电和高带宽特性。其工作 电压范围是 1.8V 到 5.5V &#xff0c;可以广泛应用在电池供电仪器仪表、新 一代的模数转换和数模转换系统中。其高带宽特性可用在 …

接口测试基础知识总结

一、HTTP 1、http请求头和响应头包含那些内容&#xff1f; 请求头信息 请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。 2、常用的请求报头如下&#xff1a; Accept&#xff1a;浏览器可接受的MIME类型。 l MIME用于设定某种扩展名的文件用哪种应…

ubuntu远程桌面连接之vnc

一、前言 ubuntu安装图形化桌面以后,有些时候出于需要会想要进行远程桌面连接。ubuntu想要进行远程桌面连接就需要vnc服务的支持,安装vnc的方法有很多,博主也试过一些方式,但是安装完后使用vnc连接工具发现是花屏,无法正常使用。后来发现一种简单的方式即可配置好vnc连接,…

计算机毕业设计 基于Java的供应商管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【赠书第16期】码上行动:用ChatGPT学会Python编程

文章目录 前言 1 ChatGPT简介 2 Python编程简介 3 使用ChatGPT学习Python编程 4 如何使用ChatGPT学习Python编程 5 推荐图书 6 粉丝福利 前言 随着人工智能技术的不断发展&#xff0c;聊天机器人已经成为我们日常生活和工作中不可或缺的一部分。其中&#xff0c;ChatGP…

MEW-UNet:医学图像分割中的频域多轴表示学习

文章目录 摘要1、简介2、相关研究2.1、医学图像分割2.2、基于ViT的技术 3、我们的方法3.1、准备工作3.2、多轴外部权重块3.3、外部权重生成器 4、实验4.1、数据集4.2、实现细节4.3、与当前最佳方法的比较4.4、消融实验4.5、可视化 5、结论声明 摘要 https://arxiv.org/pdf/231…

uniapp 无限级树形结构面包屑、单选-多选、搜索、移除功能插件,基于【虚拟列表】高性能渲染海量数据,加入动态高度、缓冲区

hyq-tree-vtw 无限级树形结构面包屑、单选-多选、搜索、移除功能 示例项目 单选-user 单选-任意一项 多选-关联下级 多选-任意一项 已选择数据弹框 说明 本插件需要使用uni-popup、uni-transition用于已选择数据弹框&#xff0c;因此需要有这些依赖,请自行导入本插件基于【虚…

Maven多模块项目架构配置介绍和实战

项目采用的是Maven多模块架构&#xff0c;项目的部分子模块的pom.xml中重复引用了相同的JAR包。很明显&#xff0c;当初在配置Maven模块的时候&#xff0c;没有考虑清楚各个模块的架构职责&#xff0c;同时也不了解Maven模块依赖的传递性。主要介绍一下Maven多模块的配置思路和…

基于深度学习大模型实现离线翻译模型私有化部署使用,通过docker打包开源翻译模型,可到内网或者无网络环境下运行使用,可以使用一千多个翻译模型语言模型进行翻译

基于深度学习大模型实现离线翻译模型私有化部署使用,通过docker打包开源翻译模型,可到内网或者无网络环境下运行使用,可以使用一千多个翻译模型语言模型进行翻译,想要什么语种直接进行指定和修改就行。 环境要求,电脑内存低于8G建议不要尝试了,有无GPU都可以运行,但是有…

geemap学习笔记041:Landsat Collection2系列数据去云算法总结

前言 去云算法是进行数据处理中所要进行一步重要操作&#xff0c;Sentinal-2数据中已经提供了去云算法&#xff0c;但是Landsat Collection2系列数据中并没有提供去云算法&#xff0c;下面就以Landsat 8 Collection2为例进行介绍。 1 导入库并显示地图 import ee import gee…

http 503 错误

503错误是一种HTTP状态码&#xff0c;表示你请求的网站或服务暂时不可用&#xff0c;通常是因为服务器过载或维护&#xff0c;你可能会看到类似这样的提示&#xff1a;503 Service Unavailable、503 Service Temporarily Unavailable、HTTP Server Error 503、HTTP Error 503 I…

Linux_CentOS_7.9配置时区及NTPdate同步之简易记录

前言&#xff1a;ntpdate命令来自英文词组”NTPdate“的拼写&#xff0c;其功能是用于设置日期和时间。ntpdate命令能够基于NTP协议设置Linux系统的本地日期和时间&#xff0c;利用NTP服务的时钟过滤器来选择最优方案&#xff0c;大大提高了可靠性和精度&#xff0c;让系统时间…

Django 快速整合 Swagger:实用步骤和最佳实践

Django &#xff0c;作为 Python 编写的一个优秀的开源 Web 应用框架&#xff0c;特别适用于快速开发的团队。对于很多场景来说&#xff0c;我们需要一份 API 文档&#xff0c;好处实在太多了&#xff1a; 提高开发效率&#xff1a;开发者可以基于 API 文档 快速学习和尝试 AP…

网络安全与IP地址:构建数字世界的前沿堡垒

网络安全是当今数字社会中不可忽视的挑战之一。而IP地址&#xff0c;作为互联网通信的基础协议&#xff0c;既是数字化时代的桥梁&#xff0c;也是网络安全的关键节点。本文将剖析IP地址在网络安全领域的作用&#xff0c;以及如何利用其特性建立有效的网络安全策略。 IP地址&a…

【Nodejs】基于node http模块的博客demo代码实现

目录 package.json www.js db.js app.js routes/blog.js controllers/blog.js mysql.js responseModel.js 无开发&#xff0c;不安全。 这个demo项目实现了用Promise异步处理http的GET和POST请求&#xff0c;通过mysql的api实现了博客增删改查功能&#xff0c;但因没有…

YOLOv8改进 | 2023检测头篇 | 利用AFPN增加小目标检测层(让小目标无所遁形)

一、本文介绍 本文给大家带来的改进机制是利用今年新推出的AFPN(渐近特征金字塔网络)来优化检测头,AFPN的核心思想是通过引入一种渐近的特征融合策略,将底层、高层和顶层的特征逐渐整合到目标检测过程中。这种渐近融合方式有助于减小不同层次特征之间的语义差距,提高特征…