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

文章目录

背景

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

以下的代码都为简化版。

1.初始化画布

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

1.创建画布

javascript 复制代码
<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.设置画布大小

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

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

javascript 复制代码
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.渲染图片

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

javascript 复制代码
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.功能:开启涂鸦

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

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

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

4.功能:添加文字

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

javascript 复制代码
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 的。

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

javascript 复制代码
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.画布平移

javascript 复制代码
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.画布缩放

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

javascript 复制代码
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的效果。

javascript 复制代码
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类型,再上传给服务器。

javascript 复制代码
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

相关推荐
荆棘鸟骑士6 天前
Hyperledger Fabric 入门笔记(十八)Fabric V2.5 测试网络部署补充 - 排序节点管理
区块链·fabric
FrancyZhou21 天前
10 Hyperledger Fabric 介绍
区块链·fabric
alex180122 天前
python Fabric在自动化部署中的应用
python·自动化·fabric
制造业保安队长1 个月前
上手体验微软全新整合的王炸平台Fabric
微软·fabric
数据猿1 个月前
Data Fabric or Data Mesh,企业数据共享选哪一个?
运维·fabric
噎住佩奇1 个月前
Fabric链码部署测试
运维·fabric
噎住佩奇1 个月前
Fabric环境部署
运维·fabric
An_s1 个月前
canvas+fabric实现时间刻度尺(二)
前端·javascript·vue.js·elementui·fabric
噎住佩奇2 个月前
Fabric部署-docker-compose安装
docker·fabric
An_s2 个月前
canvas+fabric实现时间刻度尺+长方形数据展示
前端·javascript·vue.js·elementui·fabric