最近在整理编辑器的时候我一直在纠结一件事写代码有 IDE写笔记有各种 Markdown 工具但要把“笔记”和“演示”放在一起却总得在两个窗口之间来回切。比如我记录快速排序的思路文字写了一半想跑一个动态过程给读者看还得去打开在线演示站点。这种撕裂感让我决定自己动手做一个编辑器。于是就有了lite-code-editor一款轻量级跨平台 Markdown 编辑器亮点是把算法可视化直接做进了文档里。它不追求功能堆叠默认只有一个编辑区、一个预览区再加一个可选的算法演示面板。适合算法学习者、技术写作者以及所有希望“边写边演”的开发者。1. 项目定位与核心需求拆解1.1 轻量级是第一步编辑器为什么不能“秒开”市面上并不缺 Markdown 编辑器但很多工具为了活下来拼命加功能实时协作、图床、数据库、插件市场……结果安装包越来越大启动越来越慢。我见过有人为了写一篇笔记要等编辑器转圈三秒钟这已经背离了 Markdown“专注内容”的初衷。所以 lite-code-editor 的第一个要求就是轻量。轻到什么程度双击启动到出现输入光标不能超过一秒。为了实现这个目标我把功能砍到了最少没有复杂欢迎页没有项目管理器没有内置终端没有登录体系。你打开它就是一个干干净净的文档。这种克制本身就是一种设计。Markdown 的哲学是“用最少的干扰写出结构化内容”如果编辑器本身就有一堆弹窗和侧边栏那和 Word 有什么区别轻量还体现在依赖管理上。项目不引入无用的渲染库不捆绑前端框架全家桶核心依赖控制在个位数。每次重构时我都做一个动作删掉一个包跑一遍全部功能测试如果没有任何地方报错就说明这个包是多余的。长期坚持下来包体积和内存占用都维持在很低的水平。1.2 算法可视化到底解决了什么真问题算法可视化的需求最初是我在给朋友讲排序算法时发现的。文字描述“冒泡排序每次比较相邻元素如果顺序错误就交换”读者看了三遍依然迷糊。但一旦看到柱状图中两个元素缓缓交换、一轮结束后的最大元素沉到末尾几乎立刻就能理解。问题在于传统的可视化工具独立于笔记存在。你在笔记里写“快排的平均复杂度是 O(n log n)”想配一个动态图要么嵌 GIF要么外链一个网页。GIF 是死的网页又切走了读者的注意力。如果把可视化直接塞进 Markdown 文档里就会有完全不同的体验读者读到那段冒泡排序说明文字直接在下方点击运行看着柱状图变化还能按暂停、拖进度条回到某一步仔细看。算法过程与文字描述形成互证学习效率会高很多。适合做可视化的算法很多排序、二分查找、二叉树遍历、链表反转、动态规划状态转移等。lite-code-editor 第一版聚焦排序算法和二叉树遍历因为它们最容易图型化也最能体现“状态变化”的直观价值。1.3 目标用户与使用场景这个编辑器不是给所有程序员用的它的人群非常明确算法初学者一边看笔记一边跑可视化降低理解门槛讲师或准备分享内容的工程师可以把一个算法的讲解写成 Markdown再嵌入交互演示分享出去就是一篇能动的技术文章喜欢用纯文本管理知识库的人不想要过于复杂的产品逻辑只想安静记录并偶尔验证一个算法过程。使用场景也很典型我在写一篇“二叉树的层序遍历”笔记时先用 Markdown 写好思路然后在笔记中插入一个二叉树例子点击运行就能看到节点按层弹出的动画。整个过程不用离开编辑器也不用打开浏览器。2. 整体架构与技术选型2.1 跨平台方案的取舍不做原生也不做纯网页跨平台听起来高大上落到技术选型上其实就三条路原生应用、WebView 套壳、纯网页应用。我最终选的是“本地加载的 Web 应用 系统内置 WebView 封装”原因很朴素前端生态成熟写界面效率高且在不同系统上渲染效果最一致。原生应用当然性能最强但代价是要为每个平台分别维护界面和交互逻辑。一个小项目没有足够的精力同时维护三套代码。纯网页应用虽然部署简单但无法离线打开本地文件也无法获得文件系统级的保存体验。用 WebView 封装本地页面既能把前端代码打包成桌面应用又能通过桥接接口访问本地文件系统正好卡在中间。保证“轻量”的关键是不要只缝合一个浏览器壳就完事。我做了三件事控制体积和内存复用系统自带 WebView不绑定一个巨大的运行时启动时只加载编辑器核心算法可视化模块按需懒加载不塞任何自动更新、推送、统计 SDK。有人可能会问WebView 启动不会慢吗实测下来只要不加载远程资源、不做首屏大图空页面启动速度完全可以接受。真正的瓶颈往往来自某个巨大的 JS 依赖而不是 WebView 本身。2.2 Markdown 解析与渲染链路Markdown 的渲染链路看起来简单解析文本、生成 HTML、塞进预览区。但真正做起来坑都在细节里。解析阶段我没有用正则一行行匹配而是用一个标准 Markdown 解析器把文本变成 AST抽象语法树。AST 的好处是结构清晰方便后续扩展。比如我要识别“算法可视化块”就要在 AST 中增加一个自定义节点类型而不是在 HTML 字符串里做正则替换。渲染阶段AST 先变成 HTML再注入自定义样式的 CSS。这里有个特别重要的安全点Markdown 里允许写原始 HTML如果直接渲染可能会执行恶意脚本。我采取的方法是解析时把原始 HTML 中的script标签和事件属性全部剥离只保留安全的渲染标签。Markdown 是笔记工具不是浏览器这个安全意识必须有。实时预览采用经典的左右双栏布局。左边输入右边渲染。这里的难点在于光标联动当用户在左边滚动时右边要跟着滚动到对应位置但不是精确同步而是按段落锚点对齐。我实现了一个非常朴素的算法解析每个标题的 DOM 位置左侧编辑器内容区监听滚动事件找到当前可见的首行对应的标题然后在右侧滚动到该标题附近。效果虽然不是像素级匹配但足够自然。2.3 编辑器内核选型批判过最后妥协了Markdown 编辑器最核心的部件是文本编辑内核。这一步我纠结了很久自己写一个基于 contenteditable 的编辑器还是基于纯 textarea 改造或者直接采用一个成熟的开源编辑器内核改造自己写 contenteditable 看着很酷但一遇到“光标位置保存”“IME 输入法组合”“选区与 DOM 节点同步”就让人头疼。纯 textarea 虽然稳定但做不了代码高亮和复杂补全。最终我选择了成熟内核改造。具体到内核的选型标准我关注三个点体积要小不能把整个 IDE 的编辑器都搬进来支持主题和语法高亮扩展Markdown 也是代码性能好处理几万字文本时输入不能有明显延迟。基于这些标准我选了虚拟滚动模式的内核。所谓虚拟滚动就是只渲染当前可视区域的文本行而不是把所有行生成 DOM 节点。这样即使文档有十万字浏览器里实际存在的行数也就几十行滚动时动态替换性能自然稳定。这套方案的核心开销在“高亮计算”每次输入都要重新计算整个文档语法高亮吗不是。我做了分块处理只在光标附近的一定行数内重新计算其余区域沿用上一次的高亮结果。这个优化几乎解决了所有卡顿问题。3. 算法可视化模块的详细设计3.1 自定义描述语言用一个 JSON 说清楚想演示什么算法可视化模块不是把某个在线动画工具搬进来而是要像 Markdown 一样让用户用“文本”描述一次演示。这是我的核心设计决策定义一套极简的算法可视化 DSL用户可以写在 Markdown 代码块里。我第一版定义的结构很简单用 JSON 表达即可。比如一次冒泡排序演示{ type: array-sort, algorithm: bubble, data: [4, 2, 5, 1, 3], speed: 0.8 }再比如二叉树的层序遍历{ type: binary-tree, algorithm: level-order, tree: { value: 1, left: { value: 2, left: {value: 4}, right: {value: 5} }, right: { value: 3, left: {value: 6} } } }解析时编辑器会识别 Markdown 中标记为algorithm的代码块读取 JSON 内容并转成内部可视化描述对象。这个设计的优点是文本可读、版本可控笔记放在任何地方算法演示的“源码”都在可以被 diff可以被复制甚至可以由用户手动修改参数。对于更复杂的可视化比如自定义每一步的颜色变化我会在 JSON 里增加一个states字段让用户直接提供每一步的快照。这样虽然麻烦一点但自由度极高也方便从外部程序生成可视化。3.2 布局与动画引擎Canvas 是更安全的选择算法可视化的绘制方案我对比过 DOM 动画和 Canvas 绘制。DOM 动画的优点是容易实现交互和样式元素天然支持事件缺点是元素多了以后性能急剧下降排序十来个元素没问题但一百个柱状图就会卡帧。最终我选了 Canvas。Canvas 的优势是每一帧只需要重绘需要变化的区域元素数量对性能的影响远小于 DOM。排序动画本质上是重绘一堆矩形的位置、颜色和高度Canvas 简直就是为这种场景设计的。动画引擎并不复杂核心是一个循环获取当前帧对应的算法状态根据状态绘制图形柱状图、二叉树节点、连线处理用户操作暂停、步进、恢复等待下一帧。这里的关键是“帧”怎么定义。我没有用真实时间间隔来推进算法而是把算法执行过程拆成多个“步骤”每个步骤是一份独立的快照。比如冒泡排序中一次比较和一次交换各算一步。动画引擎只需要按顺序播放快照速度参数只决定播放间隔。这样做有一个巨大的好处用户可以随时拖回任意步骤实现真正的“步进式回看”而不是单纯看动画滑过。3.3 执行控制与状态同步可视化的播放控制器有五个按钮运行、暂停、单步前进、单步后退、重置。这五个功能听起来简单但实现时要注意“当前步”的边界条件。我用一个数组保存所有步骤快照用一个整数currentStep指向当前播放位置。运行每秒推进 10 步速度可调暂停停止定时器单步前进/后退直接修改currentStep重绘当前快照重置将currentStep设为 0清空高亮。这个设计避免了“动画播放中无法干预”的尴尬。用户可以在看到某个交换操作时立刻暂停仔细看高亮的两个元素。很多学习场景下这种控制力比“流畅播放”更重要。状态同步则解决另一个问题当用户在 Markdown 编辑区修改了算法块中的data参数预览区里的可视化内容不会自动更新。这是我自己设的一个约束预览区的可视化不会因为输入而实时刷新只有在用户点击“重新加载可视化”时才重建。原因很简单拖拽滚动条时如果每一步都在重建 Canvas会非常卡而且编程演示场景里数据往往是反复调试后确定的实时刷新反而干扰。4. 核心功能实操从安装到写出第一份动态笔记4.1 五分钟跑起来lite-code-editor 的安装方式很直白下载对应系统的压缩包解压后双击运行或者通过命令行启动。命令行方式更适合开发者# 打开一个 Markdown 文件 lite-code-editor --open notes/sort.md # 新建一个文件并进入预览模式 lite-code-editor --new --preview首次启动没有任何欢迎流程直接进入空白文档。左侧是编辑区右侧是预览区。如果屏幕比较窄还可以通过快捷键切到“纯编辑模式”或“纯预览模式”这个功能对只看文字的场景很实用。4.2 写第一份带算法可视化的笔记创建一个新文件输入以下内容# 冒泡排序 冒泡排序的核心是相邻元素两两比较把较大的元素往后移动每轮结束后最大的元素会“冒泡”到数组末尾。 algorithm { type: array-sort, algorithm: bubble, data: [6, 3, 8, 2, 5, 1, 7, 4] }点击上方演示区的“运行”按钮可以看到柱状图从无序状态逐渐变得有序。第一轮结束后最大值 8 沉到最右端这个过程就是“每轮确定一个最终位置”。输入完成后切到预览视图算法区块会显示为一个带灰色边框的演示面板里面是 Canvas 绘制的柱状图。点击“运行”就能看到排序过程。 如果你不想打开预览视图也可以在编辑区下方的独立浮层里直接运行当前文档中第一个算法块。这个浮层不遮挡文字适合边写作边调试。 ### 4.3 让写作更顺手的几个小技巧 Markdown 编辑器如果没有快捷键写作效率会打对折。我默认绑定了一批常用操作 - Ctrl/Cmd B加粗 - Ctrl/Cmd I斜体 - Ctrl/Cmd K插入链接 - Ctrl/Cmd Shift C插入代码块 - Tab / Shift Tab缩进/反向缩进 - Ctrl /切换编辑区和预览区焦点。 还有一个小功能是我平时离不开的输入 # 、 、- 这类 Markdown 前缀时编辑器会自动补全后续格式比如输入 | 自动生成表格头。这个提示不是弹出补全列表而是用一段半透明的“预览格式”引导你继续输入用完按回车保留按 Esc 取消非常不打扰。 ### 4.4 导出与分享动画怎么带出门 写完带可视化的笔记总得分享给别人。lite-code-editor 支持三种导出方式 - 导出为纯 Markdown 文本 - 导出为单文件 HTML - 导出为带静态插图的 PDF。 如果你把算法可视化块放在文档里导出 HTML 时有两种选择一种是保留交互生成独立的 HTML 演示文件里面包含 Canvas 绘制逻辑分享给任何人打开都能运行另一种是导出成静态多帧图把每个关键步骤渲染成图片序列适合放进打印版文档或 PDF 里。 我通常采用第一种方式因为它的分享体验最完整。接收者不需要安装任何编辑器只需要一个现代浏览器打开 HTML 就能跟算法演示交互。 ## 5. 开发过程踩坑与性能优化实录 ### 5.1 大文档渲染卡顿虚拟滚动救了我 第一版预览区是把整个 Markdown 渲染后的 HTML 一次性插入 DOM。几千字没问题但当我拿一篇十万字的技术文档测试时滚动预览区明显掉帧输入延迟也到了难以接受的程度。 问题本质是**DOM 节点数量太多浏览器排版压力大**。解决思路是让预览区也变成“虚拟列表”只渲染当前视口附近的段落节点其他段落只占用逻辑位置不移入 DOM。 具体实现时我按 Markdown 的段落/标题把渲染结果拆成若干 Block每个 Block 对应一个 DOM 节点。预览区滚动时根据 scrollTop 计算可见 Block 范围只把范围内节点追加到 DOM 中范围外的节点移除。因为每个 Block 的高度固定内容不动态变化这个方案非常稳定。优化后预览区滚动帧率从个位数提升到了 60 帧。 ### 5.2 算法动效被输入打断解耦是关键 最初设计里算法可视化的 Canvas 和 Markdown 预览区在同一个渲染流程里。只要用户在编辑区输入预览区就会重新渲染Canvas 也会被重建一次。结果动画刚播一半被输入打断回到起点。体验非常糟糕。 我最后做了一次大重构预览区的 Markdown 渲染和算法可视化的 Canvas 渲染完全解耦。算法演示面板一旦创建就独立地挂在预览区域不参与 Markdown DOM 的更新。修改文档内容后需要点击面板上的“重新加载”才会重建演示。 当时觉得这个交互很别扭后来发现其实符合认知文档是静态内容演示是动态实验两者本来就应该独立控制。这个妥协反而让产品逻辑更清晰了。 ### 5.3 跨平台兼容的三个细节坑 跨平台开发最大的谎言是“一套代码全部搞定”。实际会遇到各种小差异 - **快捷键差异**在 Windows 上是 CtrlB在 macOS 上是 CmdB。代码里不能只监听一个组合键需要按平台映射。我封装了一个 isMac() 判断然后统一注册 mod 键运行时自动替换。 - **滚动条样式**Windows 的滚动条又宽又丑macOS 是悬浮窄条。如果不做自定义样式预览区的观感落差很大。我给滚动条单独写了针对 WebKit 的 CSS 样式并在启动时检测平台切换样式。 - **字体渲染**汉字在 Windows 上默认用微软雅黑在 macOS 上默认用苹方。直接统一指定字体反而难看。我设置了字体回退栈优先使用系统默认中文字体再回退到 sans-serif。 这些细节不会出现在功能列表里但用户一眼就能感觉出这个编辑器是“原生味”还是“网页味”。 ### 5.4 内存泄漏排查动画定时器必须彻底清理 算法可视化模块最容易出现的内存泄漏地方是播放定时器没有被清理。用户关闭演示面板时如果忘了执行 clearInterval后台的定时器会继续跑Canvas 还在持续重绘内存占用直线上升。 我写了一个专门的方法在面板销毁时统一清理 javascript destroy() { if (this.timer) { clearInterval(this.timer); this.timer null; } this.steps []; this.currentStep 0; if (this.ctx) { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } }还有一个隐蔽的坑Canvas 在页面隐藏时如果动画引擎还在跑会白白消耗 CPU。我通过监听可见性变化事件在页面不可见时自动暂停播放回到页面时再恢复。这个优化让编辑器在后台停留时的 CPU 占用几乎归零。6. 常见问题排查与后续扩展6.1 常见问题速查表根据内部测试和一段时间的试用收集我整理了几个高频问题问题可能原因解决办法启动后预览区空白Markdown 解析脚本加载失败打开开发者工具看控制台报错确认本地资源路径完整点击运行按钮没反应JSON 格式错误算法块未被解析检查代码块语言标注是否为algorithmJSON 是否合法排序动画非常快看不清默认速度设置过快把 JSON 中的speed调到 0.3 或更低修改data后演示区不变化需要手动重新加载可视化面板点击面板上的刷新按钮不会自动同步滚动大文档时预览区延迟虚拟滚动快照未更新退出预览再进入会重新计算所有 Block 高度macOS 上快捷键无效平台映射未生效重启编辑器确认系统“修饰键”设置正常6.2 数据格式扩展让更多算法可以演示第一版只支持数组排序和二叉树遍历。后续扩展不只加算法种类更关键的是扩展描述格式。比如想让用户描述一个“图”算法需要增加nodes和edges字段想演示动态规划需要支持二维表格的状态变化。这个 DSL 是核心资产必须谨慎演进。我计划下一步在 JSON 里增加renderer字段让用户可以直接指定用哪种画法柱状图、链表、树、图、矩阵等。每个 renderer 独立实现和算法的帧序列解耦。这样用户自定义算法时不必写任何 UI 代码只需要定义数据和步骤。6.3 插件机制轻量不等于封闭轻量级产品不意味着不能扩展。我计划提供一个极简的插件入口允许用户在文档里通过引入本地 JS 文件注册自定义算法渲染器。这个插件不是 npm 生态那种重量级体系纯粹是函数注册registerAlgorithmRenderer({ type: linked-list, draw(ctx, state) { // 绘制链表节点 } });为了保持安全插件 JS 会运行在隔离的 Web Worker 中不能访问文件系统也不能操作编辑区 DOM。这样就平衡了扩展性和安全性。6.4 个人经验做编辑器要有“断舍离”的勇气做 lite-code-editor 最大的体会不是技术而是取舍。很多功能做到一半会舍不得扔掉但编辑器这类工具用户是来创作的不是来探索菜单的。多一个功能就多一分维护成本也更容易破坏核心场景的流畅性。我删过最可惜的功能是“实时协作”写了一半效果也不错但每次输入同步都要触发跨进程通信严重拖慢本地编辑。最后我果断砍掉项目反而轻松了很多。另外一个小技巧与编辑器本身无关每次发布新版本前我都会用一份固定的测试文档做回归里面包含嵌套代码块、超长段落、表格、算法可视化块。只要这份文档打开不卡、渲染不报错、动画能正常运行就说明基础体验没倒退。自动化测试再好也比不上一份贴近真实使用场景的“体检文档”。如果你也在做类似工具强烈建议从第一天开始维护这么一份文档。