开源白板工具 Excalidraw 架构解读

本文讲解开源白板工具 Excalidraw 的架构设计。

版本 0.16.1

技术栈

Vite + React + TypeScript + Yarn + Husky。

脚手架原来是用的是 Create React App,但这个脚手架已经不维护了,一年多没发布新版本了。

目前市面上比较流行的 React 脚手架是 Vite,所以几个月前 Excalidraw 把脚手架替换为了 Vite,很合理。

使用了 React 去实现 UI 层,国外还是 React 流行一些。

TypeScript 用于类型标注,减少一些类型错误。

Yarn 是包管理器,没有使用 monorepo。

Husky 是 git hook 库,会在本地 git commit 做一些校验。

架构设计

模块耦合比较严重,基本核心逻辑都放在 App 类组件里,导致其所在的 App.tsx 文件行数达到 8000+ 行。

数据状态大多保存在 App 类组件的 state 属性中。

在这里插入图片描述

可以看到,编辑器的内核和 UI 是强绑定的,完完全全耦合在一起了。

如果你想把这个项目的 UI 层改成基于其他框架,比如 Vue,那基本是要重写了。

但一些复杂的方法也是会抽离出来放到一个单独的文件里,比如 group.js 里放的都是和编组相关的逻辑,但使用的是函数风格,里面全是零散的函数,没有使用面向对象的写法。

渲染方案

Excalidraw 选择了 Canvas 2D 渲染方案。

Canvas 2D 的优点是 API 做了高级封装,对开发者非常友好,缺点是其并不是为了高性能而设计的,很多底层图形计算是基于 CPU,不能很好地利用 GPU 的并行计算能力。

一句话就是性能较差,但比 SVG 好。

图形库使用了开源库 rough,一款基于 Canvas 2D 和 SVG 的手绘风图形库。

图形工具

this.state.activeTool.type 会记录当前使用的是哪个工具。

工具没有抽成类,它们的逻辑混合写在鼠标事件响应函数 handleCanvasPointerDown、handleCanvasPointerMove、handleCanvasPointerUp 中。

完全耦合在一起了。

如果我来做,我会抽一个工具管理类,然后实现各种工具类,把它们注册到工具管理类里。

在这里插入图片描述

这样写维护性非常差,你要改某一个工具,比如创建矩形的逻辑,你不得不看其他工具的逻辑,要从这一坨分支里面找到创建矩形操作会走的逻辑。

图形树

图形树的状态保存在 Scene 类中:

  • nonDeletedElements:一个拍平的图形元素数组;
  • elements:历史创建的所有图形,被删除的图形还能在这里找到。

有组的概念,图形对象的 groupIds 数组属性表示当前元素在哪些组下。

渲染

渲染图形树的入口方法是 renderStaticScene。

会顺序递归图形树的图形,将它们渲染出来。

没有使用脏矩形局部渲染。

图形拾取方案

图形拾取使用了几何法

不同图形的的渲染逻辑的判断逻辑是写在一起的。

历史记录

历史记录的逻辑在 History 类中。

Excalidraw 维护了两棵树,当图形树发生了变更时,会对一一比两棵树中图形的版本号。

如果相同,说明没发生变更,不添加新的历史记录项;否则,就创建一个历史记录项。

被更新的图形会拷贝一份,保存到 elementCache 里。

stateHistory 和 redoStack 记录的是整棵树的图形 id 和新的版本号,撤销重做时,会从中取出,去更新对应的图形为指定的版本。

国际化方案

国际化代码在 i18n.ts 文件中。

使用了状态管理库 jotai,去通知组件更新。

渲染性能优化

剔除, 视口外的图形不渲染,很基本的操作。

缓存, 图形到手绘风是需要计算的,Excalidraw 会把这个结果缓存下来,如果只是几何属性的改变,就能直接使用缓存可以减少计算量。

缩放进行防抖,延迟图形树的重渲染。

滚动事件频率很高,每一帧都重渲染,对于图形很多的情况下,Excalidraw 是吃不消的,因为 Canvas 2D 性能并不高,这时候可以考虑节流或防抖去减少重渲染的次数。

我们发现,通过滚轮放大画布时,Excalidraw 的图形是模糊的,鼠标释放时才真正重渲染。

结尾

Excalidraw 作为一款白板工具,功能很完善,美中不足的地方就是代码写得太面条。

本文透析了 Excalidraw 在图形编辑器上几个比较基础但很重要的功能,希望对你进行图形编辑器的架构有帮助。

我是前端西瓜哥,欢迎关注我,学习更多图形编辑器知识。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/94250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ的基本介绍

什么是MQ 本质是一个队列,只不过队列中存放的信息是message罢了,还是一种跨进程的通信机制,用于上下游传递信息。在互联网架构中,MQ是一种非常常见的上下游“逻辑解耦物理解耦”的消息通信服务。使用了MQ之后,信息发送…

嵌入式Linux应用开发-驱动大全-同步与互斥④

嵌入式Linux应用开发-驱动大全-同步与互斥④ 第一章 同步与互斥④1.5 自旋锁spinlock的实现1.5.1 自旋锁的内核结构体1.5.2 spinlock在UP系统中的实现1.5.3 spinlock在SMP系统中的实现 1.6 信号量semaphore的实现1.6.1 semaphore的内核结构体1.6.2 down函数的实现1.6.3 up函数的…

用于工业物联网和自动化的 Apache Kafka、KSQL 和 Apache PLC4

由于单一系统和专有协议,数据集成和处理是工业物联网(IIoT,又名工业 4.0 或自动化工业)中的巨大挑战。Apache Kafka、其生态系统(Kafka Connect、KSQL)和 Apache PLC4X 是以可扩展、可靠和灵活的方式实现端…

【文献阅读】Pocket2Mol : 基于3D蛋白质口袋的高效分子采样 + CrossDocked数据集说明

Pocket2Mol: Efficient Molecular Sampling Based on 3D Protein Pockets code: GitHub - pengxingang/Pocket2Mol: Pocket2Mol: Efficient Molecular Sampling Based on 3D Protein Pockets 所用数据集 与“A 3D Generative Model for Structure-Based Drug Desi…

MySQL进阶 —— 超详细操作演示!!!(下)

MySQL进阶 —— 超详细操作演示!!!(下) 五、锁5.1 概述5.2 全局锁5.3 表级锁5.4 行级锁 六、InnoDB 引擎6.1 逻辑存储结构6.2 架构6.3 事务原理6.4 MVCC 七、MySQL 管理7.1 系统数据库7.2 常用工具 MySQL— 基础语法大…

使用代理IP进行安全高效的竞争情报收集,为企业赢得竞争优势

在激烈的市场竞争中,知己知彼方能百战百胜。竞争对手的信息对于企业来说至关重要,它提供了洞察竞争环境和市场的窗口。在这个信息时代,代理IP是一种实用的工具,可以帮助企业收集竞争对手的产品信息和营销活动数据,为企…

python二次开发CATIA:根据已知数据点创建曲线

已知数据点存于Coords.txt文件如下: 8.67155477658819,20.4471021292557,0 41.2016126836927,20.4471021292557,0 15.9568941320569,-2.93388599177698,0 42.2181532110364,-6.15301746150354,0 43.0652906622083,-26.4843096139083,0 -31.6617679595947,-131.1513…

分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测

分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测 目录 分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测&…

C++项目:【高并发内存池】

文章目录 一、项目介绍 二、什么是内存池 1.池化技术 2.内存池 3.内存池主要解决的问题 4.malloc 三、定长的内存池 四、高并发内存池整体框架设计 1.高并发内存池--thread cache 1.1申请内存: 1.2释放内存: 1.3用TLS实现thread cache无锁访…

rabbitMQ死信队列快速编写记录

文章目录 1.介绍1.1 什么是死信队列1.2 死信队列有什么用 2. 如何编码2.1 架构分析2.2 maven坐标2.3 工具类编写2.4 consumer1编写2.5 consumer2编写2.6 producer编写 3.整合springboot3.1 架构图3.2 maven坐标3.3 构建配置类,创建exchange,queue&#x…

想要精通算法和SQL的成长之路 - 二叉树的判断问题(子树判断 | 对称性 | 一致性判断)

想要精通算法和SQL的成长之路 - 二叉树的判断问题 前言一. 相同的树二. 对称二叉树三. 判断子树 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 相同的树 原题链接 这题目典型的递归题: 如果两个节点都是null,我们返回true。如果两个节点一个nul…

centos 部署nginx 并配置https

centos版本:centos 7.8 (最好不要用8,8的很多用法和7相差很大) 一.安装nginx 1。下载Nginx安装包:首先,访问Nginx的官方网站(https://nginx.org/)或您选择的镜像站点,找…

C#学生选课及成绩查询系统

一、项目背景 学生选课及成绩查询系统是一个学校不可缺少的部分,传统的人工管理档案的方式存在着很多的缺点,如:效率低、保密性差等,所以开发一套综合教务系统管理软件很有必要,它应该具有传统的手工管理所无法比拟的…

关于算法复杂度的几张表

算法在改进今天的计算机与古代的计算机的区别 去除冗余 数据点 算法复杂度 傅里叶变换

解决java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset.的错误

文章目录 1. 复现错误2. 分析错误3. 解决问题3.1 下载Hadoop3.2 配置Hadoop3.3 下载winutils3.4 配置winutils 1. 复现错误 今天在运行同事给我的项目,但在项目启动时,报出如下错误: java.io.FileNotFoundException: java.io.FileNotFoundEx…

嵌入式系统中C++内存管理基本方法

引言 说到 C 的内存管理,我们可能会想到栈空间的本地变量、堆上通过 new 动态分配的变量以及全局命名空间的变量等,这些变量的分配位置都是由系统来控制管理的,而调用者只需要考虑变量的生命周期相关内容即可,而无需关心变量的具…

基于SSM的电动车上牌管理系统(有报告)。Javaee项目。

演示视频: 基于SSM的电动车上牌管理系统(有报告)。Javaee项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构,通过Spring SpringM…

IIS解决上传文件大小限制

IIS解决上传文件大小限制 目的&#xff1a;通过配置文件和IIS来解决服务器对上传文件大小的限制 1&#xff1a;修改配置文件&#xff08;默认为4M 值的大小根据自己情况进行修改&#xff09; <httpRuntime maxRequestLength"2048000" /> 2&#xff1a;修改IIS配…

专业图标制作软件 Image2icon 最新中文 for mac

Image2Icon是一款用于Mac操作系统的图标转换工具。它允许用户将常见的图像文件&#xff08;如PNG、JPEG、GIF等&#xff09;转换为图标文件&#xff08;.ico格式&#xff09;&#xff0c;以便在Mac上用作应用程序、文件夹或驱动器的自定义图标。 以下是Image2Icon的一些主要功…

java Spring Boot按日期 限制大小分文件记录日志

上文 java Spring Boot 将日志写入文件中记录 中 我们实现另一个将控制台日志写入到 项目本地文件的效果 但是 这里有个问题 比如 我项目是个大体量的企业项目 每天会有一百万用户访问 那我每天的日志都记载同一个文件上 那不跟没记没什么区别吗&#xff1f; 东西怎么找&#x…