闯关升级之玩转fabric的那些事

170 阅读3分钟

我正在参加「掘金·启航计划」

背景故事是这样的,老板要求我做一个so much高端的画布,要求除了简单的添加删除修改元素之外,还要求可以自定义文本,并可以动态调整文本字体大小,动态修改元素的背景颜色等等 起初是用拖拽组件的方式去实现,这是相关的demo链接woai3c.github.io/visual-drag… 原理是每次缩放和移动元素,都是重新获得元素的位置进行更新,画布元素都是以对象的形式存放,通过自定义的组件渲染元素,组件可以是视频,文本,图片等等,开放性还是很强的,但随着业务的扩展,维护成本相当的高,原因是很多功能都需要自己的扩展和维护。

接着就是无尽的冲浪,寻寻觅觅,发现了一个国外的好东西----fabric.js 。 Fabric 是一个强大而简单的 JS Canvas 库,我们能通过使用它实现在 Canvas 上创建、填充图形、给图形填充渐变颜色。 组合图形(包括组合图形、图形文字、图片等)等一系列功能。简单来说我们可以通过使用 Fabric 从而以较为简单的方式实现较为复杂的 Canvas 功能。而且它本身集成了生成图片以及导入图片的功能,总体来讲功能还是很强大的,唯一的缺点就是文档是纯英文,非常晦涩。在下通过几周锲而不舍的努力,最终还是没把文档啃完。

本文主要是介绍使用fabric.js 解决某些特殊场景的方案

引入fabric以及初始化画布

和原生canvas相似,首先都要有一个挂载的容器

 <canvas
      id="canvas"
      style="border: 1px solid #ccc"
    ></canvas>

 import { fabric } from 'fabric';
 this.canvas = new fabric.Canvas(
      'canvas',
      {
        width: 790, // 画布宽度
        height: 445, // 画布高度
        backgroundColor: '#eee', // 画布背景色
      }
    );

场景列表

1. 控制点及其样式

控制点的开放性还是很强大的,不仅可以控制某些点的显示和隐藏还可以做到圆环形的点状,默认的控制点是有9个

image.png 通过一些控制点显隐以及一些配置

      fabric.Object.prototype.setControlsVisibility(
        {
          mr: false,
          mt: false,
          mb: false,
          ml: false,
          mtr: false,
        }
      );
      
      
      
      const rect = new fabric.Rect({
          top: 30, // 距离容器顶部 30px
          left: 30, // 距离容器左侧 30px
          width: 100, // 宽 100px
          height: 60, // 高 60px
          fill: 'red',
          enableRetinaScaling: false,
          lockScalingFlip: true, // 控制缩放翻转
          transparentCorners: false,
          borderColor: '#24D7FB', // 选中时,边框颜色
          stroke: 'transparent',
          strokeWidth: 1,
          borderScaleFactor: 1, // 选中时,边的粗细:
          // borderDashArray: [5, 5, 5, 5], // 选中时,虚线边的规则
          cornerColor: '#24D7FB', // 选中时,角的颜色是
          cornerStrokeColor: '#fff', // 选中时,角的边框的颜色是
          cornerStyle: 'circle', // 选中时,叫的属性。默认rect 矩形;circle 圆形
          cornerSize: 14, // 选中时,角的大小
          cornerDashArray: [
            325, 325, 325, 325,
          ], // 选中时,虚线角的规则
          // padding: 2, // 选中时,选择框离元素的内边距:4px
          borderOpacityWhenMoving: 1, // 当对象活动和移动时,对象控制边界的不透明度
        });
        this.canvas.add(rect);  

可见其开放性很强,可以支持高强度的自定义 image.png

2. 限制最大以及最小缩放

fabric本身可通过配置 minScaleLimit 设置最小缩放,最大缩放则需要在缩放过程中去锁定,然后再解锁则可完美解决

canvas.gif

        this.canvas.on(
          'object:scaling',
          (ev) => {
            if (
              ev.target.scaleX >= 2.5
            ) {
              ev.target.lockScalingX = true;
              ev.target.scale(2.5);
            }
          }
        );
        
        rect.on('mouseover', (e) => {
          if (e.target.lockScalingX) {
            e.target.lockScalingX = false;
          }
        });

3. 画布元素不超出画布范围

要使画布元素不超出画布范围,则需要在监听元素移动的事件上做出一些计算,以左边为例 不超出的情况下,(元素的left+元素本身的宽度)<0时锁定移动并修改元素的位置,最后刷新画布。

this.canvas.on(
          'object:moving',
          (ev) => {
            const obj = ev.target;
            const objRect =
              obj.getBoundingRect();
            // 边界控制
            if (
              objRect.left +
                objRect.width <
              0
            ) {
              obj.borderColor = 'red';
              ev.target.lockMovementX = true;
              ev.target.left = 0;
              ev.target.lockMovementY = true;
            }
            this.canvas.renderAll();
          }
        );

4. 元素层级问题以及自定义层级

画布上堆积多个元素那都是家常便饭,所以控制元素的层级问题也是很重要的 fabric自身提供了bringToFront() 移到顶层 sendToBack() 移到底层bringForward() 往上一层 sendBackwards() 往下一层,而且还允许我们自定义层级,通过 rect.moveTo() 实现

5. 如何绘制圆形图片

绘制圆形图片苦恼了我几个小时,试过填充模板,裁剪图片的方式,但结果都不太理想,最后采用了group的方式,这里需要注意的是内圆的半径以及group的宽高要一致,图片要使用园进行裁剪半径保持一致

fabric.Image.fromURL(
          './logo.png',
          (img) => {
            const rect =
              new fabric.Circle({
                left: 0,
                top: 0,
                fill: 'red',
                originX: 'center',
                originY: 'center',
                radius: 50,
                hasControls: true,
              });
            const img1 = img
              .scaleToHeight(50 * 2)
              .set({
                originX: 'center',
                originY: 'center',
                clipPath:
                  new fabric.Circle({
                    radius:
                      img.height / 2,
                    originX: 'center',
                    originY: 'center',
                  }),
              });
            const group =
              new fabric.Group(
                [rect, img1],
                {
                  width: 50 * 2,
                  height: 50 * 2,
                  left: 150,
                  top: 400,
                  ...this.options,
                }
              );

            this.canvas.add(group);
          }
        );

image.png