Excalidraw 画图技术揭秘

9,071 阅读6分钟

介绍

我接触 Excalidraw 已经很长时间,它是一款非常好用的开源画图应用(库),然后个人想在今年涉足一下前端画图领域的技术,所以对它的技术实现进行了一些研究。

Excalidraw 大概从 2020 年年初开始以开源的进行建设,经历两年多的迭代现在它的代码已经相对复杂了,而我当下最关注的部分其实是在如何从 0-1 实现一个画图应用,比如用到什么技术绘图、基础版本应该哪些功能、实现这些功能的技术思路是什么等等这些,所以我花了一部分时间在 Excalidraw 最初的代码结构上,下面就谈谈我对 Excalidraw 和 画图技术的一些认识。

我的思路是先分析一款画图应用(库)应该关注的基础技术,然后在这个基础上介绍 Excalidraw 是怎么实现的。

画图基础技术

一、用什么技术实现图形元素绘制?

前端里面用来实现元素绘制的技术:canvas 或 svg,大家应当都不陌生,从我粗浅的理解来看它俩的区别不大,就是 API 不同,无非就是 canvas 是一套独立的脱离 DOM 的绘图 API,而 svg 还是我们常规意义上的 DOM,所以 svg 和普通的前端开发更近一些,可以用一套思维去处理 svg 的交互逻辑。

二、如何实现交互绘制?

交互绘制主要指用户通过鼠标拖动、键盘等实现创建或者修改元素的行为,比如:拖动创建一个矩形、拖动调整元素大小等,考虑的是这一部分的交互技术应当如何实现。

三、如何实现元素选择(点选/框选)

这个很好理解,比如点选,我们通过眼睛和大脑可以很容易的判断用户是否点击了某一个图形元素,但是技术上可能要复杂一些,尤其是涉及椭圆、贝塞尔曲线、箭头等元素时。

四、如何实现撤销/重做

撤销重做是一个图形编辑器必不可少的功能,它不复杂,但是实现起来却有很多种方式。

五、文本的考虑

绘图工具中一个必不可少的元素就是文本元素,可以只支持纯文本,有时候也需要支持富文本,比如:文字加粗、文字大小、列表支持等,对于文本的支持一定程度上影响最基础的绘图技术选型。

六、数据结构设计

这部分大概就是图形元素的数据模型设计,包含哪些字段可以完美承载所有图形元素的表示,元素的位置、大小信息、颜色、背景色等等。

Excalidraw 基础实现

一、用什么技术实现图形元素绘制

Excalidraw 底层使用 canvas 实现元素的绘制,它选择了一个基础库 roughjs 来实现 canvas 的绘制,避免直接使用 canvas 晦涩的 api。

二、如何实现交互绘制

这个说起来也没什么神秘的,Excalidraw 就是借助浏览器基础鼠标事件:mousedown、mousemove、mouseup 来匹配用户的交互意图,比如用户选择开始画一个矩形:在画布内第一次 mousedown 事件代表绘制开始(标记起始点),后面的 mousemove 事件改变矩形宽高(当前点位与起始点差值),mouseup 事件代表绘制完成。其它的图形绘制与此类似。

三、如何实现元素选择(点选/框选)

Excalidraw 最基础的版本已经支持了点选和框选,效果就很好。这里简单介绍一下它的技术实现思路,我可能也讲不太好,因为它复杂而且会用到一些数学知识。

一个简单的例子:就是矩形元素点选(只有点击矩形元素边线周围才算数,需要设定一个缓冲值),excalidraw 包含一个基础的函数 点与线段的距离 ,到矩形元素就是计算点击的位置与矩形元素四条边的距离,如果有距离小于设定的缓冲值则被认定该矩形元素被选择,这个不复杂。



点与线段的距离:

这个数学上应当是最小距离,stackoverflow 上有关于这块的算法介绍,图形解释

stackoverflow 参考

稍微复杂一些的比如椭圆,它就用到了更复杂一些的数据公式,大概就是计算目标点与椭圆横截面的最短距离,具体的计算函数介绍 Excalidraw 也出了出处:

( stackoverflow 上的链接 )

还有一些别的,比如贝塞尔曲线如何计算这些,可以理解为虽然有一些理论门槛,但是我们都可以找到应用的解决方案,大概难度就是这样吧。

四、如何实现撤销/重做

撤销重做 Excalidraw 实现的更简单,就是用一个数据组栈保存用户每一次修改数据时的一个临时状态,然后按 Ctrl + Z 时出栈最近的状态,用数据状态恢复界面状态,这应当是最简单的实现方式了。

五、文本的考虑

Excalidraw 只支持纯文本,支持通过 canvas 绘制文本。在 Excalidraw 的开始阶段文本只是简单的支持,还是通过 alert 弹框的方式支持输入的。

六、数据结构设计

可以一起看下 Excalidraw 最初的数据模型定义:ExcalidrawElement

除此之外还有一个文本的元素模型 ExcalidrawTextElement

ExcalidrawTextElement 继承自基础类型,额外包含文本内容、字体等字段

Excalidraw 技术架构

Excalidraw 最开始的代码基本上无架构模式,这里为了总结下大概梳理下它用的技术及实现思路,Excalidraw 使用 TypeScript 语言结合 React 框架编写,比较重要的一个依赖就是一个手绘风格的绘图库 Roughjs,内部也是采用数据驱动的方式来实现,通过更新数据取驱动绘图更新,一个比较不错的一个设计点就是它的数据分为:完成后数据 + 进行中数据,进行中数据就是正在画的数据,这样一来这两方面数据的绘制就可以用统一的 API 去完成更新。

结束

这是介绍 Excalidraw 的第二篇文章,原本想和第一篇一样录一个短视频的,但是觉得技术性较强的内容还是文章更好一些,就没有录,第一篇文章传送门:

5 分钟了解 Excalidraw

Excalidraw - 开源画图工具_哔哩哔哩_bilibili