前言

熟悉 canvas 的朋友想必都使用或者听说过 Fabric.js,Fabric 算是一个元老级的 canvas 库了,从第一个版本发布到现在,已经有 8 年时间了。我近一年时间也在项目中使用,作为用户简单说说感受:

  1. 方便,只有想不到,没有做不到
  2. 源码写的真好,代码规范,注释清晰
  3. 社区真匮乏,国内资源尤其少
  4. 看文档不如看源码

优缺点都很鲜明,但总的来说,如果你要做一个在线编辑类的项目,比如在线 PPT,在线制图等应用,fabric 绝对是个很好的选择。

那么这一系列文章要写什么?这里不会主要介绍如何使用 fabric,主要写的内容是把在阅读源码过程中,把涉及到原理相关的知识总结出来,比如相关图形学知识、canvas 相关、fabric 中的设计思想等的相关知识。所以,如果你现在还对 fabric 不是很了解,建议先去官网找几个 demo 试一下。

下面我们进入这次的正题,这篇文章主要介绍 fabric.canvas 涉及到的部分内容。

从创建画布开始

fabric 创建画布很简单:

const canvas = new fabric.Canvas("domId", options);
复制代码

在这样一行代码背后,fabric 主要做了下面这几件事情:

  • 创建缓存 canvas
  • 构建两层 canvas 元素:lower-canvas 和 upper-canvas
  • 绑定事件
  • 处理 retina 屏
  • ...

下面我把相关内容一一阐述。

canvas 缓存

介绍 canvas 缓存,fabric 中的缓存也是类似的道理,简单来说,就是使用一个离屏 canvas 来做预渲染,在真实画布上用 drawImage 代替直接绘制图形

我们先来看个 例子,大家可以把 FPS meter 打开,切换按钮可以看到,不使用缓存和使用缓存 FPS 值差距还是挺大的,我电脑在使用缓存的时候基本在 60fps,不使用会降到 15fps 左右。大家可以打开控制台或者在 这里 查看代码。 下面列出主要的代码片段:

class Ball {constructor(x, y, vx, vy, useCache = true) {// ...if (useCache) {this.useCache = useCache;this.cacheCanvas = document.createElement("canvas");// 离屏 canvas 宽高取要渲染图形的宽高,不可以取真实 canvas 的宽高,否则会渲染大量无用区域this.cacheCanvas.width = 2 * (this.r + BORDER_WIDTH);this.cacheCanvas.height = 2 * (this.r + BORDER_WIDTH);this.cacheCtx = this.cacheCanvas.getContext("2d");this.cache();}}paint() {// 使用缓存直接使用创建的离屏canvas,否则直接绘制图形if (!this.useCache) {ctx.save();ctx.lineWidth = BORDER_WIDTH;ctx.beginPath();ctx.strokeStyle = this.color;ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);ctx.stroke();ctx.restore();} else {ctx.drawImage(this.cacheCanvas,this.x - this.r,this.y - this.r,this.cacheCanvas.width,this.cacheCanvas.height);}}move() {// ...}cache() {// 绘制图形this.cacheCtx.save();this.cacheCtx.lineWidth = BORDER_WIDTH;this.cacheCtx.beginPath();this.cacheCtx.strokeStyle = this.color;this.cacheCtx.arc(this.r + BORDER_WIDTH,this.r + BORDER_WIDTH,this.r,0,2 * Math.PI);this.cacheCtx.stroke();this.cacheCtx.restore();}
}
复制代码

解释一下二者区别:

  • 使用缓存:在实例化每个图形的时候(渲染之前),先将图形渲染到一个离屏的 canvas 上,在渲染的时候,直接用 drawImage 将离屏的 canvas 渲染。

  • 不使用缓存: 在渲染的时候直接绘制图形

使用缓存的时候,有一点需要注意的是要控制好离屏 canvas 的大小,不可以直接取和渲染 canvas 的实际宽高,否则会渲染很多无用的空间,比如上面例子中每个离屏 canvas 的宽高只需要和对应图形的宽高一致。

this.cacheCanvas.width = 2 * (this.r + BORDER_WIDTH);
this.cacheCanvas.height = 2 * (this.r + BORDER_WIDTH);
复制代码

上述代码中主要节省时间的地方在 paint 函数中使用 drawImage会比直接绘制图形节省时间,那么是否所有场景都是这样呢?我们再来看下面这个 例子.

这个例子和上面的只有绘制图形的代码不同:

// 从复杂图形变成了简单图形
cache() {this.cacheCtx.save();this.cacheCtx.lineWidth = BORDER_WIDTH;this.cacheCtx.beginPath();this.cacheCtx.strokeStyle = this.color;this.cacheCtx.arc(this.r + BORDER_WIDTH,this.r + BORDER_WIDTH,this.r,0,2 * Math.PI);this.cacheCtx.stroke();this.cacheCtx.restore();
}
复制代码

只是cache方法中把复杂图形变成了简单的图形。但实际效果相差甚远,使用缓存和不使用性能差距并不大,甚至不使用时 fps 值还更高一些。

所以看来图形的复杂度,直接会影响 canvas 缓存的效果,我们在开发过程中,也不能盲目引入缓存,要权衡利弊。fabric 中缓存是默认开启的,同时也可以设置 objectCaching 为 false 禁用。

lower-canvas 和 upper-canvas

如果大家细心的话应该会发现,当我们执行new fabric.Canvas('domeId')的时候,在页面上 dom 元素就改变了,fabric 复制了一层 canvas 盖在了我们定义的 canvas 上面:

fabric 这样设计将渲染层和交互层做了分离,lower-canvas 只负责渲染元素;所有的交互,比如框选,事件处理都在 upper-canvas 上。

顺便提一下,fabric 提供了渲染静态画布的方法,如果你的画布不需要任何交互,只用来展示,那么可以用new fabric.StaticCanvas('domId', options)来初始化,这时候 dom 结构中就只有一个 canvas,没有 upper-canvas 了。

说到这里,很多同学可能会想到,事件是怎样绑定的呢?其实两个 canvas 大小等属性都是一致的,所以坐标也是可以对应上的,比如在 upper-canvas 上某个位置点击了一下,那么就可以去 lower-canvas 上就可以用这个坐标去找是否点击到了一个元素,那么问题来了,如何判断一个点在一个图形中呢?

如何判断点在图形中

这个问题网上有个比较普遍的方案,就是通过画一条射线,通过交点奇偶性来判断。如下图:

  1. 设目标点 P,使 P 点向任意一个方向画一条射线,保证不与图形的顶点相交;
  2. 记录射线与图形的交点数量 n;
  3. n 为奇数时,P 就在图形内,反之则在图形外。

而 fabric 中并没有用这种方法,原因很简单,这个算法是有前提的:发出的射线不能与图形任何顶点相交。 这个前提对于我们主观来判断是很简单的,但程序中处理可能就需要大量的代码去判断是否与交点相交,如果相交再重新生成一条射线。

fabric 中使用的算法对上述算法进行了改进,我们结合下图来解释:

其中 e1 ~ e5 分别为多边形的边,P 为目标点,黑色实心点为多边形的顶点,r 为 P 延 X 轴发出的射线(不同于上面的方法,这里我们约定 r 射线只能延 X 轴发出)。

  1. 设目标点 P,使 P 延 X 轴方向画一条射线( y=Py ),设 intersectionCount = 0
  2. 遍历多边形的所有边,设边的顶点为 p1, p2
    1. 如果 p1y < Py,而且 p2y < Py,跳过(也就是这条边在 P 点下方)
    2. 如果 p1y >= Py,而且 p2y >= Py,跳过(也就是这条边在 P 点上方)
    3. 否则,设射线与这条边的交点为 S,如果 Sx >= Px,intersectionCount加 1
  3. 最终如果intersectionCount为奇数,则在图形内,反之则在图形外。

判断的部分用代码实现类似:

// point 目标点,lines多边形的所有边
function checkPoint(point, lines) {let intersectionCount = 0;let { x, y } = point;for (let i = 0; i < lines.length; i++) {let line = lines[i];// 两个顶点let { p1, p2 } = line;if ((p1.y < y && p2.y < y) || (p1.y >= y && p2.y >= y)) {continue;} else {const sx = ((y - p1.y) / (p2.y - p1.y)) * (p2.x - p1.x) + p1.x;if (sx >= x) {intersectionCount++;}}}return intersectionCount % 2 === 0;
}
复制代码

这里是个简单的例子。同时 这里 可以获取完整代码。

处理 Retina 屏

Retina 屏幕模糊的问题,直接给出处理方法,就不展开说了。

  1. canvas.width, canvas.height 放大至 dpi 倍
  2. canvas.style.width, canvas.style.height 设为原始 canvas 宽高
  3. ctx 缩放 dpi 倍

代码:

function initRetina(canvas, ctx) {const dpi = window.devicePixelRatio;canvas.style.width = canvas.width + "px";canvas.style.height = canvas.height + "px";canvas.setAttribute("width", canvas.width * dpi);canvas.setAttribute("height", canvas.height * dpi);ctx.scale(dpi, dpi);
}
复制代码

查看例子,完整代码

小结

本篇文章主要针对fabric.canvas模块,介绍了相关 canvas 缓存,fabric 中判断点在图形中的算法以及如何处理 retina 屏幕的知识,作为系列的第一篇文章,可能会有很多问题,如有错误及意见,欢迎批评指正。

参考文献:
idav.ucdavis.edu/~okreylos/T…
www.geog.ubc.ca/courses/kli…
www.cnblogs.com/axes/p/3567…
fabricjs.com/docs/

转载于:https://juejin.im/post/5cc9b79a6fb9a0320f7df408

我从 fabric.js 中学到了什么相关推荐

  1. Canvas实用库Fabric.js使用手册

    简介 什么是Fabric.js? Fabric.js是一个可以简化Canvas程序编写的库. Fabric.js为Canvas提供所缺少的对象模型, svg parser, 交互和一整套其他不可或缺的 ...

  2. 强大的Canvas开源库Fabric.js简介与开发指南

    什么是Fabric.js? Fabric.js 是一个强大且简单的Javascript HTML5 Canvas库. 官网地址:http://fabricjs.com/ 为什么要使用Fabric.js ...

  3. fabric.js和高级画板

    本文介绍fabric.js框架使用,以及使用fabricjs打造一个高级画板程序. 高级画板功能介绍 全局绘制颜色选择 护眼模式.网格模式切换 自由绘制 画箭头 画直线 画虚线 画圆/椭圆/矩形/直角 ...

  4. HTML5 Canvas JavaScript库 Fabric.js 使用经验

    首先,表明我的态度:采用 Flash 才是最优方案,不建议使用 HTML 5 的 Canvas 做一些生产/工业级的网页应用. Flash的优势一是浏览器支持好,二是代码成熟稳定.而HTML5 的 C ...

  5. angelajs中ajax,Fabric.js Triangle angle属性用法及代码示例

    在本文中,我们将看到如何使用FabricJS绘制固定角度的画布Triangle.画布三角形表示三角形是可移动的,可以根据需要拉伸.此外,当涉及初始笔触颜色,高度,宽度,填充颜色或笔触宽度时,可以自定义 ...

  6. Fabric.js添加辅助线的方法

    Fabric.js高级点的教程1–添加辅助线的方法 Fabric.js添加辅助线的方法 Fabric.js 非常的强大,但是国内的资源教程很少,最近想加个功能给元素添加辅助参考线(类似 演示 ).这样 ...

  7. Fabric.js 自由绘制椭圆

    theme: smartblue 持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情 本文简介 点赞 + 关注 + 收藏 = 学会了 本文讲解在 Fabric ...

  8. html5无法绘制线条,Html5画布 - 使用fabric.js绘制完美线条或不使用

    我正在创建一个游戏,我需要在不同类型的屏幕分辨率和缩放下实现HTML5上的完美画布线.Html5画布 - 使用fabric.js绘制完美线条或不使用 很容易理解我说的,简单地粘贴在两个不同的代码转换成 ...

  9. Fabric.js 铅笔笔刷

    本文简介 点赞 + 关注 + 收藏 = 学会了 fabric.js 的铅笔其实是继承基础画笔的一个工具,在基础画笔的基础上多了"拐角平滑度"等配置项. 本文讲解铅笔的基础用法以及常 ...

最新文章

  1. 随机抽样java_Reservoir Sampling 蓄水池抽样算法,经典抽样
  2. VirtualBox安装Kali
  3. 读《代码整洁之道》前四章浅显印象 和 我所见的不整洁代码引以为戒
  4. sqlserver获得到当前游标中存在的数据行数
  5. 蛋白对接_JCIM | 金属蛋白分子对接程序哪家强?七种对接程序的基准测试
  6. 华为“方舟编译器”到底是啥?一文看懂TA如何让手机性能再突破
  7. css3实现浮动元素垂直水平居中
  8. 实施ERP管理系统的流程有哪些
  9. 二阶魔方万能还原公式_2阶魔方教程简单口诀(二阶魔方还原公式口决是什么?)...
  10. PostGIS Raster 空间查询
  11. shader 获取法线_Unity Shader-法线贴图(Normal)及其原理
  12. cloudstack GuestNetwork Ingress-Egress rule
  13. 集成创新,拓展兼容--红旗Linux桌面版5.0隆重发布(转)
  14. 图像对齐(图像配准)方法记录
  15. 《启示录:打造用户喜爱的产品》 -读后感
  16. zookeeper选举和ZAB协议
  17. html+js+css 调用jquery 工人信息管理功能(增删改查)前端实现,以及调用实现鼠标拖尾粒子效果的js库
  18. C++ 模板元编程的应用有哪些,意义是什么?
  19. PHP获取一年有多少天、一个月有多少天(最全最新)
  20. Click House设置远程登陆及修改默认用户名密码

热门文章

  1. AI人工智能在2020年的7个发展趋势
  2. 英语学习单词篇(9)
  3. idea 文件夹右键新建没有Class
  4. 4K画质修复神器,模糊照片秒变高清
  5. 数据库系统--关系代数中笛卡尔积,选择,投影,自然连接,除运算
  6. 如何设置html打印区域大小,excel如何设置打印区域
  7. table 竖直排列(垂直排列)
  8. 如何给SVN版本库瘦身
  9. 广西刚公布 广东、陕西就紧随其后,2022年二级建造师成绩公布先后排名竞争激烈
  10. Pandas处理Excel超简单