canvas库fabric.js踩坑

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了canvas库fabric.js踩坑相关的知识,希望对你有一定的参考价值。

参考技术A 众所周知,canvas的api繁杂,对一般的前端er来说不太友好,加上平时一般也不会自己手写canvas,所以一般开发者对canvas的涉猎可能并不太深(我看红宝书的时候canvas是直接跳过的)。而当需要使用canvas开发一些定制化的需求时,echarts,antv系列,可能就无法满足了,这个时候或许fabric会是一个比较好的选择,fabric提供一种类似面向对象的方法来编写canvas,比原生稍微方便一些(然鹅官方文档太难看懂了)

近期的一个项目中,有这么一个需求:拖拽缩放元素并且进行连线,本来我第一反应是用antv/g6去实现的,但是需要对拖拽的元素缩放并且拖拽的容器需要放文字和图表,如果使用g6的话,缩放容器,里面的内容改变不太利索(实际是我对g6不太熟),另一个重要的问题是g6元素里面放图表的话只能放g2(而且需要单独安装插件)并且不支持诸如tooltip等等功能,简单来说只能用个阉割版的(示例: https://antv-g6.gitee.io/zh/examples/item/customNode#lineChartNode )。因此我最初想的是使用 vue-grid-layout ( github && 文档 )进行拖拽与缩放,画线使用canvas。这样做的好处是第三方组件已经把拖拽和缩放功能全都封装好了,dom元素嵌入echarts和文本缩放也相当方便(vue-echarts的autoresize,文本使用flex布局加overflow:auto),当然画线又是一个大问题,关键点就是线要和拖拽的元素接上,简而言之就是坐标计算了。考虑到画布里面还要放图(拖拽的元素连线到图上)以及要实现连线的时候鼠标移动需要不停的重绘线,最终在同事的推荐下决定使用fabric.js来实现canvas部分。然后就发现这东西用起来一言难尽...

1.官方文档
就算你英语很好看他的文档也会很别扭的,建议直接看 官方DEMO 找自己要的,不懂的百度谷歌,最后把查找文档作为补充以及检查是否有新版api和网上的古早文章不同。

2.在vue中使用

目前只能这样用

3.绘制本地图片有问题
我尝试过 fabric.Image.fromURL('xxx/xxx.png',function()) 以及 new Image().src 这两种发现貌似都不能放本地图片地址(类似 @/assets/... 这种),可能是我使用的方式不对,最后只剩下一种方法可用了:

这种方法首先需要在页面上放一个隐藏的img元素,结果一开始fabric还读不到只能通过 onload 事件来获取,但这样会导致画布重绘时无法执行onload,最后一个绘制图片被我写成这样了

sendToBack 方法是为了确保在后面画线的时候线能在图的上面一层显示(貌似fabric是按照先后绘制顺序排层级的,先绘制的层级最高,于是我们需要将图的层级降到最低)

可能是页面结构太复杂的缘故,上面的方法有小概率执行时图片还没加载好,导致最后画布里面其它内容都出来了结果最重要的图没了,最终我搞出来的解决办法是,在img标签上直接绑定load事件,执行load时将组件内设置的状态修改,并监听这个状态的变化来执行图片渲染到canvas画布的过程。

画图(画线除了selectable其它类似,因为我的项目需要选中线)

因为我需要点击线的时候弹出删除菜单,所以不能在初始化的时候直接 skipTargetFind: true ,我要做的是去除选中的样式和大部分功能,保留选中时能获取到选中对象,一旦这个属性设为true则会取消所有选中样式和功能,不需要在canvas对象里面再单独配置了。

第二,计算三次贝塞尔曲线的控制点,这里面用了向量运算...

网上找的检测屏幕缩放比例的方法(可以检测到系统分辨率改变)

然后在初始化fabric对象时需要重新计算宽高(canvasLayout为画布上一级的父元素)

pageZoom主要在拖动元素时计算元素与线的连接点坐标用到了,这个系统里面只要vue-grid-layout元素有改变,我就要重新计算线的起点并重绘线,通过这种办法实现了dom元素和canvas元素的绑定,听起来很low的样子,不过最后功能是都实现了。

参考文章(还有些讲fabric的api的文章找不到了...)
https://github.com/hujiulong/blog/issues/1

fabric视频教程(我还没看过,可能有些内容存在过时)
https://www.bilibili.com/video/BV1at411q7bt

Fabric.js:100% 宽度的画布可能吗?

【中文标题】Fabric.js:100% 宽度的画布可能吗?【英文标题】:Fabric.js: Canvas with 100% width possible? 【发布时间】:2013-02-19 08:43:47 【问题描述】:

当使用 'canvas = new fabric.Canvas('foo')' 时,Fabric 会将带有 width=100% 的 css 类的画布元素转换为类似的内容:

<div class="canvas-container" style="position: relative" >
  <canvas class="upper-canvas"></canvas>
  <canvas class="lower-canvas" id="c"></canvas>
</div>

包装 div 以及两个画布元素都获得了样式标签和固定的宽度/高度。在初始化方面,我只发现 canvas.setWdith 虽然只接受数值(没有百分比值)。

有没有办法初始化 Fabric 以尊重/使用给定的 100% 宽度?

更新: 示例:http://jsfiddle.net/thomasf1/kb2Hp/

【问题讨论】:

似乎无法将宽度设置为 100%,当尝试通过 CSS 强制设置时,Fabric 停止按预期工作。 解决方案 - ***.com/a/8486324/104380 【参考方案1】:

我不确定您是否可以明确告诉 Fabric 使用 100% 宽度,但也许可以通过 $('.canvas-container').width()document.getElementById('canvas-container').clientWidth 获得 canvas-container 的宽度,然后在该值上使用 canvas.setWidth?例如:

canvas.setWidth($('.canvas-container').width());

提示:还记得在窗口调整大小时设置此项,以防止任何内容溢出。

【讨论】:

小改动,但“canvas-container”是作为类注入的,而不是 id。所以它应该是 '.canvas-container' 而不是 '#canvas-container' 只是为了给 Nick Mitchell 的评论添加一些细节,Fabric 将您的主机画布包装在一个带有 canvas-container 类的 div 中。【参考方案2】:

因为canvas element、width 和height 的性质需要以像素为单位。因此,Fabric 不会在画布上使用百分比值。

attribute unsigned long width;
attribute unsigned long height;

【讨论】:

【参考方案3】:

通过使用它的对象和窗口的innerWidth和innerHeight来调整fabric的画布的大小

(function()
  var canvas = new fabric.Canvas('app');

  window.addEventListener('resize', resizeCanvas, false);

  function resizeCanvas() 
    canvas.setHeight(window.innerHeight);
    canvas.setWidth(window.innerWidth);
    canvas.renderAll();
  

  // resize on init
  resizeCanvas();
)();

【讨论】:

以防万一有人想给出特定的 div 尺寸 canvas.setHeight($('.yourDiv').height()); canvas.setWidth($('.yourDiv').width()); 如果页面需要一些时间来设置其布局,将调用推迟到resizeCanvas() 是有意义的。对我来说,window.setTimeout(resizeCanvas, 2000) 的工作更加可靠。

以上是关于canvas库fabric.js踩坑的主要内容,如果未能解决你的问题,请参考以下文章

在fabric.js中将Canvas下载为PNG,给出网络错误

Html5 Canvas + fabric.js 的独立堆肥

将图像从计算机上传到 Fabric.JS Canvas

如何在fabric js中覆盖canvas.add()方法

Fabric.js:有啥方法可以在 JSON.stringify(canvas) 生成的 Json 中保存 url 而不是 svg 图像的路径?

Fabric.js Canvas - 使一种对象始终位于另一种对象下方