使用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.rhkb.cn/news/233611.html

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

相关文章

基于深度学习的停车位关键点检测系统(代码+原理)

摘要&#xff1a; DMPR-PS是一种基于深度学习的停车位检测系统&#xff0c;旨在实时监测和识别停车场中的停车位。该系统利用图像处理和分析技术&#xff0c;通过摄像头获取停车场的实时图像&#xff0c;并自动检测停车位的位置和状态。本文详细介绍了DMPR-PS系统的算法原理、…

DDIM学习笔记

写在前面&#xff1a; &#xff08;1&#xff09;建议看这篇论文之前&#xff0c;可先看我写的前一篇论文&#xff1a; DDPM推导笔记-大白话推导 主要学习和参考了以下文章&#xff1a; &#xff08;1&#xff09;一文带你看懂DDPM和DDIM &#xff08;2&#xff09;关于 DDIM …

[Vulnhub靶机] DriftingBlues: 2

[Vulnhub靶机] DriftingBlues: 2靶机渗透思路及方法&#xff08;个人分享&#xff09; 靶机下载地址&#xff1a; https://download.vulnhub.com/driftingblues/driftingblues2.ova 靶机地址&#xff1a;192.168.67.21 攻击机地址&#xff1a;192.168.67.3 一、信息收集 1.…

【docker笔记】Docker网络

Docker网络 容器间的互联和通信以及端口映射 容器IP变动时候可以通过服务名直接网络通信而不受到影响 常用命令 查看网络 docker network ls创建网络 docker network create XXX网络名字查看网络源数据 docker network inspect XXX网络名字删除网络 docker network rm…

深入理解并解析Flutter Widget

文章目录 完整代码程序入口构建 Widget 结构定义 widget 状态定义 widget UI获取上下文关于build()build() 常用使用 完整代码 import package:english_words/english_words.dart; import package:flutter/material.dart; import package:provider/provider.dart;void main() …

PPT模板(100套IT科技互联网风)

哈喽&#xff0c;小伙伴们&#xff0c;最近是不是都在准备年终总结、年终述职&#xff0c;一个好的PPT模板是编写报告的开端。我最近也在准备年终总结报告&#xff0c;一块整理了一些PPT模板。这些模板适用于各种IT科技互联网相关的场合&#xff0c;如产品发布会、项目提案、工…

数据结构—排序—选择排序

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、选择排序 1、基本思想 2、直接选择排序 3、选择排序的代码实现 二、堆排序 2.1算法讲解 2.2堆排序的代码实现 总结 前言 世上有两种耀眼的光芒&#xff0…

chatGPT带你学习设计模式 (二)抽象工厂模式(创建型模式) GURU

深入理解抽象工厂模式 引言 在面向对象编程中&#xff0c;对象的创建是一个常见且关键的挑战。尤其在需要管理一系列相关对象的创建时&#xff0c;传统的对象创建方法&#xff08;如直接使用 new 关键字&#xff09;可能导致代码的高耦合和低灵活性。这时&#xff0c;抽象工厂…

JSUDO|加速度与阿里云合作云产品

电讯&#xff1a;深圳市加速度软件开发有限公司【加速度jsudo】&#xff0c;与阿里云计算有限公司&#xff08;简称“阿里云”&#xff09;达成合作&#xff0c;双方将在电商、企业管理等应用软件领域就云产品和应用软件更深层次合作。 加速度软件长期以来&#xff0c;一直与阿…

jquery图形验证码

效果展示 js图形随机验证码&#xff08;表单验证&#xff09; html代码片段 <form class"formwrap"><div class"item"><input type"text" id"code_input" value"" placeholder"请输入验证码"/>…

网络调试 UDP1,开发板用动态地址-入门6

https://www.bilibili.com/video/BV1zx411d7eC?p11&vd_source109fb20ee1f39e5212cd7a443a0286c5 1, 开发板连接路由器 1.1&#xff0c;烧录无OS UDP例程 1.2&#xff0c;Mini USB连接电脑 1.3&#xff0c;开发板LAN接口连接路由器 2. Ping开发板与电脑之间通信* 2.1 根据…

探索PyTorch优化和剪枝技术相关的api函数

torch.nn子模块Utilities解析 clip_grad_norm_ torch.nn.utils.clip_grad_norm_ 是 PyTorch 深度学习框架中的一个函数&#xff0c;它主要用于控制神经网络训练过程中的梯度爆炸问题。这个函数通过裁剪梯度的范数来防止梯度过大&#xff0c;有助于稳定训练过程。 用途 防止…

java实验室预约管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java servlet 实验室预约管理系统是一套完善的java web信息管理系统 系统采用serlvetdaobean&#xff08;mvc模式)&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数 据库&#xff0c;系统主要采用B/S模式开发。开发环境为T…

性能优化-OpenMP基础教程(四)-Android上运行OpenMP

本文主要介绍如何在一个常规的Android手机上调试OpenMP程序&#xff0c;包括Android NDK的环境配置和使用JNI编写一个OpenMP程序运行在Android手机中。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#…

ElasticSearch使用Grafana监控服务状态-Docker版

文章目录 版本信息构建docker-compose.yml参数说明 创建Prometheus配置文件启动验证配置Grafana导入监控模板模板说明 参考资料 版本信息 ElasticSearch&#xff1a;7.14.2 elasticsearch_exporter&#xff1a;1.7.0&#xff08;latest&#xff09; 下载地址&#xff1a;http…

WSL使用Ubuntu 20.04版本运行py-bottom-up-attention的记录,及其可能错误的解决方法

文章目录 1. 切换linux的镜像2. 安装gcc3. 查看显卡驱动4. 安装gcc版本5. wsl安装cuda 10.16. 新建虚拟环境8. 安装依赖包9. 运行代码错误运行的所有历史命令如下 WSL使用Ubuntu 20.04版本运行py-bottom-up-attention的记录&#xff0c;及其可能错误的解决方法 github代码地址…

线性渐变linear-gradient——线性渐变实现虚线斜线条纹

1.效果图 2.html <div class"box"><div class"address-edit"></div></div> 3.css <style>*{margin: 0;padding: 0;}.box{position: relative;width: 100vw;height: 300px;background-color: #fff;}.address-edit::before…

JetPack组件学习ViewModel

ViewModel的使用 1.需要先创建ViewModel类&#xff0c;继承自ViewModel重写onclear方法&#xff0c;使得页面销毁的时候能够走到自定义的onClear方法中 class MyViewModel : ViewModel() {//共享数据的核心在于拿到同一个LiveData实例&#xff0c;也就是拿到同一个ViewModel实…

如何成为ChatGPT 优质Prompt创作者

如何提问&#xff1f; 我想让你成为我的Prompt创作者。你的目标是帮助我创作最佳的Prompt&#xff0c;这个Prompt将由你ChatGPT使用。你将遵循 以下过程&#xff1a;1.首先&#xff0c;你会问我Prompt是关于什么&#xff1f;我会告诉你&#xff0c;但我们需要 通过不断的重复来…

Redis高并发高可用(主从复制、哨兵)

复制 在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器,满足故障恢复和负载均衡等需求。Redis也是如此,它为我们提供了复制功能,实现了相同数据的多个Redis 副本。复制功能是高可用Redis的基础,哨兵和集群都是在复制的基础上实现高可用的。 默认…