需求:我司是做AIGC项目最近和地铁项目有关需要实现海报效果图,并且需要使用画布拖拽和修改上传删除等等功能 当时连续加班花了10个工作日搓出来 实现挺简单的但是Canvas数据处理还是挺麻烦的 大概功能如图下

首先我们需要引入Fabric.js 这个库封装好了原生的Canvas的Api使用uniapp和vue项目
步骤一:引入Fabric.js库
npm i fabric --save
步骤二:安装less或者sass因为需要更改canvas样式需要用到deep(使用sass注意更改style的lang)
npm i less
npm i less-loader
步骤三:创建DOM节点 uniapp和纯vue项目的canvas创建是不一样的 (巨坑)
Uniapp的canvas外层会嵌套uniapp-canvas会阻碍到Fabric.js读取但是纯vue项目则不会

因为Canvas代码量比较多 我这边把坑就额外标记出来 建议复制到vscode查看更佳 (下列代码复制即可使用 亲测有效 一定要按照步骤来)
-
- <view id="card-canvas-content">
- view>
-
- <script>
- import {
- fabric
- } from 'fabric'; // 引入库
- let canvas = null // 最好设置成全局的不要定义到data里面
- export default {
- data() {
- return {
- }
- },
- mounted() {
- this.createCanvasDom('card-canvas') // 初始化节点
- this.init() //初始化画布
- },
- methods: {
- // 创建画布
- createCanvasDom(elID) {
- var cardContent = document.getElementById('card-canvas-content'); //选取父节点
- var cardCanvas = document.createElement('canvas'); // 创建canvas标签
- cardCanvas.setAttribute('id', elID); // 设置canvas的ID
- cardContent.appendChild(cardCanvas); // 插入canvas到父节点中
- },
- // 初始化画布
- init() {
- // 画布初始配置
- canvas = new fabric.Canvas('card-canvas', { // 获取画布节点传入 初始化节点ID
- width: 200, // 后续可以更改
- height: 200,
- });
-
- }
- }
- }
- script>
- <style lang="less" scoped>
- /deep/ #card-canvas{
- border: 1px solid red ;
- }
- style>
步骤四:canvas基础配置(这些配置是可选的可以根据项目情况自行配置)
- // 初始化画布
- init() {
- // 画布初始配置
- canvas = new fabric.Canvas('card-canvas', { // 获取画布节点传入 初始化节点ID
- width: 200, // 后续可以更改
- height: 200,
- });
- fabric.Object.prototype.padding = 10;
- fabric.Object.prototype.controls.mtr.withConnection = false;
- // 解决动态生成文字模糊问题
- fabric.Object.prototype.objectCaching = false;
- fabric.Object.prototype.borderColor = 'dodgerblue';
- // 修改控制点的形状,默认为`rect`矩形,可选的值还有`circle`圆形
- fabric.Object.prototype.cornerStyle = 'circle';
- // 修改控制点的填充色为白色
- fabric.Object.prototype.cornerColor = 'white';
- // 修改控制点的大小为10px
- fabric.Object.prototype.cornerSize = 10;
- // 设置控制点不透明,即可以盖住其下的控制线
- fabric.Object.prototype.transparentCorners = false;
- // 修改控制点的边框颜色为`gray`灰色
- fabric.Object.prototype.cornerStrokeColor = 'gray';
- // 单独修改旋转控制点距离主体的纵向距离为-20px
- fabric.Object.prototype.controls.mtr.offsetY = -20;
- // 单独修改旋转控制点,光标移动到该点上时的样式为`pointer`,一个手的形状
- fabric.Object.prototype.controls.mtr.cursorStyle = 'pointer';
-
- }
第五步:初始化数据建议抽离一个方法出去避免都写在init里面过于冗杂 (在init里面调用放置末尾)
- init() {
- this.initDate()
- }
- // 初始化数据
- initDate() {
- //更改画布后续宽高 可以根据后台的图片动态设置canvas大小
- canvas.setDimensions({
- width: this.width,
- height: this.height
- });
- // fabric内置的方法 用于重新渲染canvas 在字体以及大小更换 图片替换都需要调用此方法 否则canvas不会更新新数据
- canvas.renderAll();
- this.$nextTick(() => {
- // canvas渲染完成之后最后就会执行一般是处理loading效果的
- });
- }
步骤六:导入背景图 (巨坑 当时我研究了贼久 一直报错图片污染导致后续图片无法导出canvas)
我们需要把远程的背景图片转成base64的格式不然导出canvas会报错 这也是跨域 网上有很多解决方案都没用
![]()
代码比较长我就抽离出去写注释 这些方法统一在initDate里面调用
- // 图片转码
- getURLBase64(url) {
- //将远程图片下载本地成为base64图片
- let _this = this;
- return new Promise((resolve, reject) => {
- var xhr = new XMLHttpRequest();
- xhr.open('get', url, true);
- xhr.responseType = 'blob';
- xhr.onload = function() {
- if (this.status === 200) {
- var blob = this.response;
- var fileReader = new FileReader();
- fileReader.onloadend = function(e) {
- var result = e.target.result;
- _this.imageUrl = result;
- resolve(result);
- };
- fileReader.readAsDataURL(blob);
- }
- };
- xhr.onerror = function() {
- reject();
- };
- xhr.send();
- });
- },
设置Canvas背景图片
- // 设置/修改画布背景图 bgUrl:转码后的背景图片 canvasDOM:canvas的实例变量名
- setBackgroundImage(bgUrl, canvasDOM) {
- canvasDOM.setBackgroundImage(bgUrl, canvasDOM.renderAll.bind(canvasDOM), { // canvas实例变量
- originX: 'left',
- originY: 'top',
- left: 0,
- top: 0,
- level: 1, // 层级
- bgUrl // 路径
- });
- },
使用:
- async initDate() {
- //更改画布后续宽高 可以根据后台的图片动态设置canvas大小
- canvas.setDimensions({
- width: this.width,
- height: this.height
- });
- // fabric内置的方法 用于重新渲染canvas 在字体以及大小更换 图片替换都需要调用此方法 否则canvas不会更新新数据
- canvas.renderAll();
- // 背景地址 自己随便填一个线上的即可
- let bgUrl = await this.getURLBase64('https://img1.baidu.com/it/u=395218774,2205974617&fm=253&app=138&size=w931&n=0&f=GIF&fmt=auto?sec=1718730000&t=15d143401501c32f552f4157563acaf4');
- // 设置背景图片
- this.setBackgroundImage(bgUrl, canvas)
- },
此时是页面呈现的样式

步骤六:插入文字
- // 插入文字 text:文本 canvasDOM:canvas的实例变量名
- setTextFont(text,canvasDOM) {
- const textDOM = new fabric.Textbox(text, {
- top: 0, // 位置
- left: 0,
- fontSize: 19, // 文字大小
- fill: 'red', // 颜色
- level: 0, // 层级
- fontFamily: '思源黑体 CN Heavy',
- splitByGrapheme: true
- });
- canvasDOM.add(textDOM); //添加文字
- canvasDOM.bringToFront(textDOM); //优先级
- }
使用
- async initDate() {
- //更改画布后续宽高 可以根据后台的图片动态设置canvas大小
- canvas.setDimensions({
- width: this.width,
- height: this.height
- });
- // fabric内置的方法 用于重新渲染canvas 在字体以及大小更换 图片替换都需要调用此方法 否则canvas不会更新新数据
- canvas.renderAll();
- // 背景地址 自己随便填一个线上的即可
- let bgUrl = await this.getURLBase64('https://img1.baidu.com/it/u=395218774,2205974617&fm=253&app=138&size=w931&n=0&f=GIF&fmt=auto?sec=1718730000&t=15d143401501c32f552f4157563acaf4');
- // 设置背景图片
- this.setBackgroundImage(bgUrl, canvas)
- // 插入文本
- this.setTextFont('这是一行文本',canvas)
- },
页面效果 此时页面的文字是可以进行拖拽等操作的

(内容有点多 先更新三分之一 后面再抽空更新)