好吧,那我就自己造个文本编辑器

Lobsters Hottest 工具

摘要

作者描述了构建自定义文本编辑器的过程,实验了canvas、contenteditable和textarea等方法,并讨论了Web开发中的性能和可访问性等挑战。

<p><a href="https://lobste.rs/s/1malyo/fine_i_ll_build_my_own_text_editor">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/09/01 11:47

# 行,那我自己造一个文本编辑器!来源:https://dbushell.com/2026/09/01/text-editor/ 纯人工,非AI制作。2026年9月1日 星期二 “现在没人做像Sublime Text那样的软件了”(https://dbushell.com/2026/08/07/sublime-text/)引起了很多人共鸣。如今的软件都是垃圾。这让我思考:我正好擅长写垃圾代码!为什么我不能自己造个文本编辑器? VS Code基于Monaco编辑器(https://microsoft.github.io/monaco-editor/)构建,那玩意儿简直是**一团糟**。我上车VS Code很晚,因为多年来我的Intel inside™ Mac太慢了。直到我换了Apple Silicon,这个问题才解决。如果这是行业标准,那我还有很多犯错的空间。 ## Canvas 我的第一个实验是在`<canvas>`元素上渲染所有内容。你看不出来,但你的CPU正以每秒60-120帧的速度进行大量工作来渲染那个画面。缺乏交互性对文本编辑器来说是个明显的问题。我列出了“最小可行”功能清单并逐一实现: - 指针按下定位文本光标 - 方向键移动文本光标 - 高亮当前行 - 输入文本 - 花哨的光标动画 下面这个演示是可交互的,随便点点打打字。别跟我扯Vim快捷键:闭嘴,我有更要紧的问题要解决。Canvas没有免费提供任何功能。在众多理想功能中,我缺少: - 文本选择 - 撤销/重做历史 - 多行粘贴 - 溢出滚动 最后一点至关重要。人生苦短,没时间实现自定义弹性滚动条。我决定作弊,对隐藏元素使用浏览器原生溢出。一个`<div>`的大小被调整为与Canvas文本匹配,其滚动位置用于计算`<canvas>`上的渲染偏移。我对进展感到满意,但也有些沮丧,因为`<canvas>`完全无法访问(可访问性)。我本可以继续添加文本选择等功能,但这无法解决根本的可访问性问题。我有了个更好的主意。 ## Content editable 与其在`<canvas>`上渲染文本,我可以在溢出`<div>`中原生渲染,并用`contenteditable`属性(https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/contenteditable)使其可编辑。该属性有一个`plaintext-only`值,非常适合代码。所有内容都保持在单个文本节点内。 `contenteditable="plaintext-only" spellcheck="false">` 必须禁用`spellcheck`等属性以避免输入延迟激增。想知道我花了多少天才发现这个解决办法吗?*好几天!*使用`contenteditable`能获得原生文本选择、撤销历史等。浏览器免费提供了大量优秀的可访问性支持。`Selection API`(https://developer.mozilla.org/en-US/docs/Web/API/Selection)提供了我用于继续渲染自定义文本光标的度量数据。`::selection`也可用,所以我也可以为其设置样式。我将原生`caret-color`(https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/caret-color)设为不可见,这可能不太好。`contenteditable`技术很有前途,但我注意到超过一定字符数后会出现奇怪的性能问题。Chromium浏览器的表现比WebKit和现在的Firefox(或其它基于Gecko的)要差,而且表现不稳定。 ## Textarea 相比纯文本的`contenteditable`,一个简单的`<textarea>`是否可行?简而言之:是的。事实证明,对于较长文本,`<textarea>`的性能表现要好得多。在最后这个演示中,我添加了语法高亮。我最初的计划是在`contenteditable`元素上使用自定义`::highlight`(https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::highlight)。`<textarea>`无法使用CSS高亮,所以需要第三层。为了演示,我为可见行添加了一些HTML代码,以应用MicroLighter(https://davatron5000.github.io/microlighter/)。 **编辑:**有人告诉我,新的OpaqueRange API(https://olliewilliams.xyz/blog/opaquerange/)为`<textarea>`解锁了自定义高亮——不错!太多的CSS高亮也是另一个性能瓶颈。更健壮的解决方案是使用Tree-sitter(https://tree-sitter.github.io/tree-sitter/)生成语法树,并遍历它仅为可见行生成高亮。我曾希望完全避免虚拟滚动,但我可以使用反向粘性技术(https://pierre.computer/writing/on-rendering-diffs#:~:text=systems%20and%20browsers.-,The%20Inverse%20Sticky%20Technique,-For%20CodeView%2C%20many)来改进它。或者我可以回到`contenteditable`,因为我要编辑的文件大小不会触及性能墙。总之,看起来不错,对吧?看起来像90%的文本编辑器,只有1%的功能。从这里开始,画完剩下的猫头鹰(https://knowyourmeme.com/memes/how-to-draw-an-owl)就相当直接了。我忍不住想继续画下去,但一想到所有诸如Tab缩进这样的小事就算了……目前我只是劫持Tab键来插入两个空格…… 我上面的演示未经过优化,可访问性也远非完美,但至少我没有从一个注定失败的起点开始。在`<canvas>`上渲染会是一场噩梦。我把这个项目先搁置,留待雨天再做吧。 --- JavaScript字符串和文本范围使用UTF-16编码单元工作。很容易天真地引入错误。我的演示里肯定充满这类问题。最后用一个代码示例来极客式地狙击一下(nerd snipe): ```javascript "🍋🟩".length; // 5 [..."🍋🟩"].length; // 3 const segmenter = new Intl.Segmenter("en", {granularity: "grapheme"}); [...segmenter.segment("🍋🟩")].length; // 1 ```

相似文章

约600行的GTK+Adwaita Markdown编辑器

Lobsters Hottest

一个约600行的紧凑型GTK+Adwaita分屏Markdown编辑器实现,使用GTK WebView和CDN加载的marked.js与KaTeX。它作为一个受Apostrophe启发的原型,展示了构建此类应用如今已变得多么容易。

全程原生,直到你需要文本

Hacker News Top

一位资深 macOS/iOS 开发者讲述了使用苹果原生框架(SwiftUI、AppKit、TextKit)实现支持 Markdown 的聊天界面的挣扎,最终发现像 Electron 这样的基于 Web 的技术为富文本渲染提供了更实用的解决方案。

不要自己造轮子…

Lobsters Hottest

作者将“不要自己造轮子”的原则扩展到Web开发领域,反对自定义实现滚动、链接导航、文本选择等浏览器原生行为。