使用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, // 旋转角度,默认为0originX: "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 -> fileconst 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.mzph.cn/news/598468.shtml

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

相关文章

实战AI大模型:构建和优化深度学习巨兽的关键技术【文末送书-15】

文章目录 前言一.模型设计1.1 硬件加速1.2 模型部署 二.模型深度和宽度的平衡2.1引入注意力机制2.1 残差连接 三.实战AI大模型【文末送书-15】3.1 粉丝福利&#xff1a;文末推荐与福利免费包邮送书&#xff01; 前言 随着人工智能领域的迅猛发展&#xff0c;大规模深度学习模型…

《3D数学基础-图形和游戏开发》阅读笔记 | 3D数学基础 (学习中 1.5更新)

文章目录 3D数学基础矢量/向量概述 - 什么是向量单位矢量&#xff1a;只关注方向不关注大小 数学运算矢量的加法与减法减法的几何意义计算一个点到另一个点的位移矢量的点积与叉积 矩阵方阵几何意义 - 表示空间坐标的变换组合变换 矩阵的乘法变换的分类 矩阵的行列式 3D数学基础…

Linux第7步_设置虚拟机的电源

设置ubuntu代码下载源和关闭“自动检查更新”后&#xff0c;就要学习设置“虚拟机的电源”了。 用处不大&#xff0c;主要是了解”螺丝刀和扳手形状的图标“在哪里。 1、打开虚拟机&#xff0c;点击最右边的“下拉按钮”&#xff0c;弹出对话框&#xff0c;得到下图&#xff…

CEEMDAN +组合预测模型(CNN-Transformer + ARIMA)

往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较-CSDN博客 风速预测&#xff08;一&#xff09;数据集介绍和预处理-CSDN博客 风速预测&#xff08;二&#xff09;基于Pytorch的EMD-LSTM模型-CSDN博客 风速预测&#xff…

栈的数据结构实验报告

一、实验目的&#xff1a; 1、理解栈的定义&#xff1b; 2、利用栈处理实际问题。 二、实验内容&#xff08;实验题目与说明&#xff09; 利用栈实现数据的分类&#xff0c;将输入的整数以奇偶为标准分别存放到两个栈中&#xff0c;并最终从栈1和栈2输出偶数和奇数序列。 …

原来圣诞树可以这么做

先看结果 从上到下依次是&#xff1a; 2^0 2^1 2^2 2^3 2^4 2^5 2^6 2^7 ... 依次排下去&#xff0c;最后加4个单位数的数字 原来代码的世界里还有这个美。^V^

全志R128系统RTOS使用说明

使用串口访问设备 使用USB TypeC 连接线连接开发板 USB转串口 的接口&#xff0c;安装串口驱动程序&#xff1a;CH341SER.EXE 到设备管理器找到需要的串口&#xff0c;这里是 COM8 使用串口访问工具 PuTTY 打开串口&#xff0c;这里是 COM8&#xff0c;波特率 115200。 打开之后…

添加一个编辑的小功能(PHP的Laravel)

一个编辑的按钮可以弹出会话框修改断更天数 前台 加一个编辑按钮的样式&#xff0c;他的名字是固定好的 之前有人封装过直接用就好&#xff0c;但是一定放在class里面&#xff0c;不要放在id里面 看见不认识的方法一定要去看里面封装的是什么 之前就是没有看&#xff0c;所以…

如果PostgreSQL有两层nginx代理,会发生什么事?

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 1. 前言 PostgreSQL默认只能本机连接&#xff0c;若要在别的客户端远程连接pgsql&#xff0c;则需要修改配置文件pg_hba.conf&a…

如何将ElementUI组件库中的时间控件迁移到帆软报表中

需求:需要将ElementUI组件库中的时间控件迁移到帆软报表中,具体为普通报表的参数面板中,填报报表的组件中,决策报表的组件与参数面板中。 这三个场景中分别需要用到帆软报表二开平台的ParameterWidgetOptionProvider,FormWidgetOptionProvider,CellWidgetOptionProvider开…

无心剑小诗《银婚颂》

银婚颂 二十五个春秋共一轮 你是岁月赠予我最亮的星辰 从青春燃烧到岁月沉稳 你的笑颜,我永恒的晨昏 春花烂漫是你眼里的璀璨 夏日蝉鸣是彼此故事的和弦 秋叶纷飞诉说漫天情缘 冬雪纯洁见证温暖的牵绊 月光洒满每段共享小径 星光点染每个深情的夜晚 风雨同舟铸就铜墙铁壁 携…

书生·浦语大模型实战1

书生浦语大模型全链路开源体系 视频链接&#xff1a;书生浦语大模型全链路开源体系_哔哩哔哩_bilibili 大模型之所以能收到这么高的关注度&#xff0c;一个重要原因是大模型是发展通用人工智能的重要途径 深度信念网络&#xff1a; &#xff08;1&#xff09;又被称为贝叶斯网…

科技助力教育:数字化如何改变家校社协同育人?

近年来,随着社会的快速发展,教育的责任已不再仅局限于学校。家庭、学校和社会协同育人理念,正成为促进教育高质量发展的关键要素。 2023年初,教育部等十三部门联合印发《关于健全学校家庭社会协同育人机制的意见》,提出到“十四五”时期末,形成更加完善的由“学校积极主导、家…

Elasticsearch零基础实战

分享后可优化点&#xff08;待完成&#xff09; java es8 查询如何打印查询入参 &#xff1f;&#xff08;直接执行的json&#xff09; es自定义分词器 如何实现&#xff1f; kibana 监控jvm分子分母是什么 &#xff1f; es如何 改索引结构&#xff1f; 修改数据原理 分享…

【动态规划】C++算法:115.不同的子序列

作者推荐 【动态规划】【字符串】扰乱字符串 本文涉及的基础知识点 动态规划 LeetCode115 不同的子序列 给你两个字符串 s 和 t &#xff0c;统计并返回在 s 的 子序列 中 t 出现的个数&#xff0c;结果需要对 109 7 取模。 示例 1&#xff1a; 输入&#xff1a;s “rab…

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机本身的数据保存(CustomData)功能(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机本身的数据保存&#xff08;CustomData&#xff09;功能&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的数据保存&#xff08;CustomData&#xff09;功能的技术背景CameraExplorer如何使用图像剪切&#xff0…

服务器执行rm命令时自动记录到审计日志中

目的 当在服务器上执行类似于 rm 命令时&#xff0c;自动记录该命令执行的时间&#xff0c;在哪里执行的&#xff0c;删除的什么文件&#xff0c;记录到审计日志中&#xff0c;能够查找到某些文件丢失原因 配置 # 需要root权限&#xff0c;sudo不行&#xff0c;这里假设执行…

工具网站DefiLlama全攻略:从零学习链上数据使用与发现

DefiLlama 是一个 DeFi(去中心化金融)信息聚合器,其主要功能是提供各种 DeFi 平台的准确、全面数据。DefiLlama 致力于在不受广告或赞助内容影响的情况下为用户提供这些数据,以确保信息内容的透明度和公正性,该平台聚合来自多个区块链的数据,让用户能够全面了解 DeFi 格局…

conda操作使用教程

一 conda介绍 Conda 是一个开源的包管理系统和环境管理系统&#xff0c;用于在 Linux、Windows 和 macOS 上管理 Python 包和依赖项&#xff0c;java有maven, python有conda,它是python开发者的最爱。 Conda 的核心功能&#xff1a; 包管理&#xff1a;安装、更新、删除 Pytho…

nginx配置图片服务器

目录 一&#xff1a;访问流程 二&#xff1a;缓存服务器配置 三&#xff1a;上传图片直接上传到图片服务器 四&#xff1a;加快图片访问 一&#xff1a;访问流程 访问缓存服务器(上面安装nginx反向代理到图片服务器&#xff0c;对外提供服务)->图片服务器 二&#xff1…