news 2026/4/16 19:45:04

用Excalidraw打造中文手写风格画板

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Excalidraw打造中文手写风格画板

用 Excalidraw 打造中文手写风格画板

在技术团队的日常协作中,一张随手勾勒的草图往往比千言万语更高效。无论是架构讨论、流程推演,还是产品原型构思,可视化表达始终是沟通的核心工具。而 Excalidraw 正是这一场景下的明星选手——它以极简界面和“手绘风”视觉效果著称,让数字白板拥有了纸笔般的亲和力。

但当我们真正投入中文环境使用时,却总感觉哪里不对劲:英文文本用了 Virgil 这类手写字体,线条歪歪扭扭很自然;可一旦输入中文,立刻变成规整冷峻的黑体或宋体,像是一张温柔的手稿里突然插入了打印标签,风格割裂感扑面而来。

有没有办法让中文也“写”得随意一点?答案是肯定的。本文将带你从零开始,深度定制 Excalidraw,引入一款优雅的开源中文字体,实现真正的“全手写风”统一体验,并部署为可共享的 Web 应用。

整个过程不仅解决了一个具体问题,更是一次对现代前端项目架构的实战演练:如何理解 Monorepo 结构、干预字体加载机制、处理服务端渲染兼容性,以及完成静态站点发布。


准备工作:获取并运行 Excalidraw 源码

要定制一个开源项目,第一步永远是跑通它的本地开发环境。

前往 Excalidraw 官方仓库,点击右上角Fork按钮复制到你的 GitHub 账户下,然后克隆到本地:

git clone git@github.com:yourname/excalidraw.git cd excalidraw

建议创建一个独立分支用于本次功能开发:

git checkout -b feature/chinese-handwriting-font

这样后续即使官方更新主干代码,也能通过 rebase 或 merge 平滑合并,保留我们的修改痕迹。

接下来安装依赖并启动服务:

yarn install yarn run start

Excalidraw 使用 Yarn 管理依赖,基于 Vite 构建。几分钟后浏览器会自动打开 http://localhost:3000,你应该能看到熟悉的绘图界面,可以自由添加形状、文本、箭头等元素。

✅ 提示:若启动失败,请确认 Node.js 版本是否为 v16+,这是项目推荐的基础运行环境。


字体选择:寻找一款“会呼吸”的中文字体

我们想要的不是普通的书法字,而是一种带有轻微抖动、笔画粗细变化、仿佛真实书写留下的痕迹——这种质感才能与 Excalidraw 的“手绘风”融为一体。

关键要求如下:
- 支持常用汉字(至少覆盖 GBK)
- 视觉风格自然流畅,避免机械感
- 免费可商用,规避版权风险

目前市面上有不少优秀开源字体平台可供筛选:
-猫啃网(maoken.com):专注免费可商用字体分享,标注清晰
-字由(hellofont.cn):设计师常用资源库
-Google Fonts 中文镜像:部分字体支持

本文选用“平方雨桐体”(YuTong Type Foundry 出品),这是一款圆润自然、略带连笔感的开源字体,其轻重变化恰到好处,非常适合模拟手写情境下的非均匀运笔效果。

下载.ttf格式的文件后,重命名为Yutong.ttf,便于后续引用。


集成字体:打通客户端与服务端的渲染链路

Excalidraw 采用 Monorepo 架构,字体系统分布在多个子包中。我们需要同时配置前端显示和服务端导出能力,否则会出现“页面看着正常,导出图片变黑体”的尴尬情况。

1. 放置字体文件

Yutong.ttf复制到两个关键目录:

cp Yutong.ttf packages/excalidraw/fonts/assets/ cp Yutong.ttf public/

前者用于构建时打包进字体模块,后者供 Node.js 服务直接提供静态资源访问。

2. 声明字体:CSS @font-face

编辑packages/excalidraw/fonts/assets/fonts.css,末尾添加:

@font-face { font-family: "Yutong"; src: url(./Yutong.ttf) format("truetype"); style: normal; display: swap; }

⚠️ 注意:必须写format("truetype")而非format("ttf"),这是 WOFF 规范的要求,否则浏览器可能拒绝解析。

3. Node 端注册字体(导出 PNG/SVG 必须)

编辑packages/excalidraw/index-node.ts,找到已有字体注册代码:

registerFont("./public/Virgil.woff2", { family: "Virgil" }); registerFont("./public/Cascadia.woff2", { family: "Cascadia" });

在其下方加入:

registerFont("./public/Yutong.ttf", { family: "Yutong" });

这一步至关重要。没有它,当你点击“Export as PNG”时,Canvas 将无法绘制该字体,导致中文回退为默认字体甚至乱码。

4. 添加预加载提升性能

为了避免首次加载时出现字体闪烁(FOIT/FOUT),需在 HTML 中预加载字体资源。

修改excalidraw-app/index.html,在<head>内添加:

<link rel="preload" href="/Yutong.ttf" as="font" type="font/ttf" crossorigin="anonymous" />

如果你使用了自定义 Vite 插件处理字体注入(如scripts/woff2/woff2-vite-plugins.js),也应同步添加此 preload 标签。

5. 扩展字体枚举常量

打开packages/excalidraw/constants.ts,查找FONT_FAMILY对象:

export const FONT_FAMILY = { Virgil: 1, Helvetica: 2, Cascadia: 3, Excalifont: 5, Nunito: 6, "Lilita One": 7, };

为了避免与未来官方新增字体冲突,我们使用一个较大的数值作为键值:

Yutong: 999

最终结果为:

export const FONT_FAMILY = { // ... existing fonts Yutong: 999 };
6. 添加字体选择器 UI

编辑packages/excalidraw/components/FontPicker/FontPicker.tsx,这是顶部工具栏中的字体下拉菜单组件。

在选项数组中新增一项:

{ value: FONT_FAMILY.Yutong, icon: FontFamilyHandDrawnIcon, // 复用手绘图标 text: "手写体 (中文)", testId: "font-family-yutong", },

💡 可选优化:你可以将"手写体 (中文)"提取为国际化字段,写入lang/zh-CN.json等语言包,实现多语言支持。

7. 注册字体元数据(防止 fallback)

最后一步,确保字体被正确纳入 Excalidraw 内部的字体管理系统。

编辑packages/excalidraw/fonts/index.ts,在_register调用后添加:

import Yutong from "./assets/Yutong.ttf"; // ... _register("Yutong", FONT_METADATA[FONT_FAMILY.Excalifont], { uri: Yutong, });

这里复用了Excalifont的度量信息(如行高、基线偏移),虽然不完全匹配,但在大多数场景下足以保证排版一致性。如果追求极致精准,也可以单独测量并定义新的FONT_METADATA条目。


功能验证:看看“写的字”像不像人写的

重新启动应用:

yarn run start

进入页面后操作如下:
1. 点击左侧 “T” 文本工具
2. 输入一段中文,例如:“用户登录流程”
3. 在顶部样式面板中选择新添加的“手写体 (中文)”选项
4. 观察文字是否呈现自然笔触

✅ 成功标志:中文字体呈现出轻微的粗细波动和圆润转角,与英文手绘线条风格协调一致,整体画面不再有割裂感。

此时你已经拥有了一款真正意义上的“双语手绘风”画板。


构建与部署:让它被世界看见

为了让团队成员或公众也能使用这个定制版本,我们可以将其部署为静态站点。推荐使用GitHub Pages实现免费托管。

1. 安装 gh-pages 工具
yarn add -D gh-pages
2. 添加 deploy 脚本

package.jsonscripts字段中增加:

"deploy": "gh-pages -d excalidraw-app/build"

完整示例如下:

"scripts": { "start": "concurrently \"yarn:start:app\" \"yarn:start:svr\"", "build": "yarn build:app", "deploy": "gh-pages -d excalidraw-app/build" }
3. 配置相对路径(适配二级目录)

如果你计划部署在https://yourname.github.io/excalidraw-zh/这样的子路径下,必须启用相对路径。

编辑excalidraw-app/vite.config.mts

export default defineConfig({ base: './', // 改为相对路径 // ... });

否则所有资源请求都会指向根目录/assets/...,导致 404 错误。

4. 执行构建与部署

依次运行:

yarn run build yarn run deploy

gh-pages会自动创建一个名为gh-pages的分支,并推送构建产物。几分钟后,前往仓库 Settings > Pages 即可看到部署成功的提示。

访问链接形如:https://yourname.github.io/excalidraw-zh/

即可全球访问你专属的中文手写风格 Excalidraw 画板


常见问题与解决方案(实战踩坑记录)

❌ 问题1:字体未生效,显示为方块或默认字体

原因分析
-@font-faceformat('ttf')不合法
- 浏览器缓存旧 CSS 或字体文件
- 字体路径错误或未正确复制到public/

解决方法
1. 检查fonts.css是否使用format("truetype")
2. 清除浏览器缓存(Ctrl + F5 强刷)
3. 打开 DevTools Network 面板,确认Yutong.ttf是否成功加载

❌ 问题2:导出 PNG/SVG 时中文乱码或缺失

根本原因:Node.js 环境未注册字体。

修复方式:务必确认已在index-node.ts中调用registerFont(),且路径相对于public/目录正确无误。

❌ 问题3:部署后报错Could not parse as value list

典型错误信息:

DOMException: The source provided ('url(/assets/Yutong-Bla.ttf) format('ttf')') could not be parsed...

根源:Excalidraw 内部根据扩展名自动生成format(xxx),而.ttf被误判为ttf,但标准应为truetype

修复方案

修改packages/excalidraw/fonts/ExcalidrawFont.ts中的getFormat方法:

private static getFormat(url: URL) { try { const pathname = new URL(url).pathname; const parts = pathname.split("."); if (parts.length === 1) return ""; let ext = parts.pop()?.toLowerCase(); switch (ext) { case "ttf": return "format('truetype')"; case "woff": return "format('woff')"; case "woff2": return "format('woff2')"; case "otf": return "format('opentype')"; default: return ""; } } catch (error) { return ""; } }

重新构建后,该问题即可消除。


进阶方向:结合 AI 快速生成图表

Excalidraw 不只是一个绘图工具,它还能成为AI 驱动的知识生产引擎

想象一下这样的场景:你在会议中说了一句“帮我画个电商系统的微服务架构”,下一秒白板上就出现了带注释的组件图——这不是科幻,而是完全可以实现的工作流。

推荐集成路径:
方案说明
Excalidraw + Mermaid + AI Prompt利用 AI 解析自然语言生成 Mermaid 代码,再通过转换器导入 Excalidraw
Custom AI Plugin开发插件调用 OpenAI API,输入描述返回 JSON 元素数组
Obsidian 插件联动若你使用 Obsidian,可通过其 Excalidraw 插件实现笔记内嵌 AI 生图

示例 Prompt:

“请生成一个包含用户登录、订单管理、支付网关的电商系统架构图,使用手绘风格。”

这类能力不仅能极大提升敏捷开发中的原型效率,也让非技术人员能轻松参与系统设计。


这种高度集成的设计思路,正引领着智能协作工具向更自然、更高效的方向演进。而你刚刚完成的,不只是一个字体替换,更是一次对开源生态的创造性回应——当工具不再只是“可用”,而是真正“好用”于你的母语文化时,创造力才真正获得了自由。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 13:40:03

Jetson Nano配置PaddlePaddle并实现OCR测试

Jetson Nano 上从零部署 PaddlePaddle 与中文 OCR 实践 在嵌入式 AI 应用日益普及的今天&#xff0c;如何在资源受限的边缘设备上实现高效、稳定的深度学习推理&#xff0c;成为开发者面临的核心挑战之一。NVIDIA Jetson Nano 凭借其小巧体积、低功耗和 GPU 加速能力&#xff…

作者头像 李华
网站建设 2026/4/12 17:37:20

42、Perl引用的使用与嵌套数据结构构建

Perl引用的使用与嵌套数据结构构建 1. 引用基础与子程序参数传递 在Perl中,引用是一个强大的工具。当修改 @array2 时,它不会影响 @array1 ,因为它们是内容独立的不同数组。而 $arrayref 中对 @array1 的引用,会和 @array1 的当前内容相同,因为引用指向的是和 …

作者头像 李华
网站建设 2026/4/16 21:04:38

Wan2.2-T2V-5B预训练权重开放,支持本地部署

Wan2.2-T2V-5B预训练权重开放&#xff0c;支持本地部署 你有没有过这样的经历&#xff1f; 灵感突然闪现&#xff0c;想做个短视频验证想法&#xff0c;结果刚写完脚本就卡在了渲染上——等了半小时&#xff0c;视频还没跑完&#xff0c;热情早已冷却。 但现在不一样了。 最…

作者头像 李华
网站建设 2026/4/16 21:28:07

47、Perl编程的进阶知识与实用技巧

Perl编程的进阶知识与实用技巧 1. XML数据处理 在Perl中处理XML数据时,可将XML文档映射到变量 $computers ,它是一个哈希引用。这个哈希有一个元素,键为 computer ,其值是另一个哈希的引用,该哈希的键由XML文件中 computer 元素的属性名表示。每个这样的哈希成员的…

作者头像 李华
网站建设 2026/4/16 18:31:01

全网热议!2025年最佳单北斗GNSS变形监测系统推荐榜单

在2025年&#xff0c;市场上的单北斗GNSS变形监测系统种类繁多&#xff0c;各具特色。许多系统不仅能够实时监测地震、滑坡等地质灾害&#xff0c;还能为桥梁等基础设施提供稳定的变形监测服务。这些设备通常依托先进的GNSS技术&#xff0c;结合高精度传感器&#xff0c;确保数…

作者头像 李华
网站建设 2026/4/16 16:57:19

黄金高位AI动能骤减,“非农”与“恐怖数据”AI冲击波蓄势待发

摘要&#xff1a;本文通过构建基于机器学习与深度学习的多维度数据分析模型&#xff0c;结合自然语言处理&#xff08;NLP&#xff09;对非农数据进行语义解析&#xff0c;运用强化学习算法对市场情绪进行动态捕捉&#xff0c;分析现货黄金价格关键就业数据发布背景下的波动逻辑…

作者头像 李华