惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Palo Alto Networks Blog
MyScale Blog
MyScale Blog
D
DataBreaches.Net
S
SegmentFault 最新的问题
L
LangChain Blog
Blog — PlanetScale
Blog — PlanetScale
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Microsoft Security Blog
Microsoft Security Blog
N
News and Events Feed by Topic
S
Security Affairs
The Register - Security
The Register - Security
Recent Announcements
Recent Announcements
Last Week in AI
Last Week in AI
I
InfoQ
人人都是产品经理
人人都是产品经理
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 叶小钗
O
OpenAI News
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
AI
AI
V
V2EX - 技术
T
Threatpost
Webroot Blog
Webroot Blog
Hacker News: Ask HN
Hacker News: Ask HN
The Hacker News
The Hacker News
Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
P
Proofpoint News Feed
阮一峰的网络日志
阮一峰的网络日志
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The Cloudflare Blog
W
WeLiveSecurity
Cyberwarzone
Cyberwarzone
S
Security @ Cisco Blogs
S
Schneier on Security
Jina AI
Jina AI
Hacker News - Newest:
Hacker News - Newest: "LLM"
TaoSecurity Blog
TaoSecurity Blog
Help Net Security
Help Net Security
Google Online Security Blog
Google Online Security Blog
P
Privacy International News Feed
C
Check Point Blog
MongoDB | Blog
MongoDB | Blog
I
Intezer
Vercel News
Vercel News
NISL@THU
NISL@THU
aimingoo的专栏
aimingoo的专栏
酷 壳 – CoolShell
酷 壳 – CoolShell

码云笔记

Vue 的 v-cloak 和 v-pre 指令有什么作用? 去哪里买抖音号?抖音小号在线购买-抖音小号出售批发 实名闲鱼小号出售|闲鱼账号批发|咸鱼发布账号|出售闲鱼发布账号 zfb号购买-支付宝小号购买-实名支付宝购买号-企业支付宝购买-支付宝小号批发 个人支付宝实名账号出售|v2/v3支付宝账号出售|实名支付宝小号购买 闲鱼账号哪里有卖-闲鱼实名账号购买的途径 Java注解的实现原理是什么? Agentation MemPalace 《置身钉内》之好词好句好段摘抄 AI有声书革命!万象有声全自动生产线,7元/万字颠覆网文有声化行业 详解CentOS 7跨版本迁移Rocky Linux9 生产环境安全升级教程 Java中序列化与反序列化的含义是什么? 18. Python 标准库之 Json 模块
现代富文本编辑器为什么最终都会抛弃 DOM?
公民阿赞 普通 · 2026-06-15 · via 码云笔记

AI 概述

早期富文本编辑器直接操作DOM,易出现光标、撤销、特殊节点处理等各类问题。现代编辑器转而维护独立文档状态与AST,将DOM仅作为渲染载体,自主实现撤销栈、适配输入法。其状态驱动的运行逻辑和渲染机制,与React理念相似,也更适配AI编辑等复杂场景。

目录

文章目录隐藏

  1. 1. 原生 API 所存在的问题
  2. 2. 现代编辑器的设计
  3. 3. 现代编辑器越来越像 React
  4. 4. 写在最后

现代富文本编辑器为什么最终都会抛弃 DOM?

很多前端开发第一次接触富文本编辑器时,都会觉得:

<div contenteditable="true"></div>

似乎已经够用了。浏览器已经帮你实现好了:

  • 光标
  • 输入
  • 选区
  • 粘贴
  • 删除
  • undo

甚至还能直接document.execCommand("bold");,于是很多人会天然认为:富文本编辑器本质上不就是“操作 DOM” 吗?

但真正开始做编辑器后,你会发现,现代编辑器最后会逐渐演变成:

状态机 + 事务系统 + 渲染引擎 + 协同系统 + 输入法兼容层

尤其 AI 出现之后,这种趋势越来越明显。今天这篇文章,我们就来聊聊为什么现代富文本编辑器,最终一定会从直接操作 DOM 演变成维护自己的文档状态。

1. 原生 API 所存在的问题

来看一个非常真实的场景,比如你正在做 AI 编辑器,用户选中hello world,然后点击AI 改写,模型返回Hi world,很多人第一反应可能是:

const selection = window.getSelection();
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
range.deleteContents();
range.insertNode(
  document.createTextNode("Hi world")
);

看起来似乎没什么问题。但真正运行后,你很快会发现,整个编辑器开始逐渐失控。比如:

  • 光标丢失
  • undo 失效
  • mention 被拆碎
  • 输入法异常
  • selection 错乱

这里最重要的一点是你会第一次意识到,编辑器真正维护的,根本不只是 HTML。

为什么只靠 DOM 会越来越痛苦?

前面这种做法,本质上是用户操作->直接修改 DOM。也就是说,DOM 既是页面展示结果,也是编辑器数据源。这是最传统的编辑器实现方式。

比如:

range.surroundContents(strong);

或者:

editor.innerHTML

直接作为最终文档保存。但问题在于:DOM 并不理解“文档”。它只理解:

  • div
  • span
  • textNode
  • strong

浏览器真正维护的是页面结构,而不是文档语义。这个差别特别重要。

一个特别经典的问题:Mention

比如:

hello @User1

浏览器里的 DOM 可能长这样:

<p>
  hello
  <span data-id="user1">
    @User1
  </span>
</p>

从浏览器角度:

这只是一个 span。

但从编辑器角度,它其实是一个完整的 mention 节点。因为:

@User1

并不只是字符串。它背后还绑定着:

  • 用户 ID
  • 用户资料
  • 通知关系
  • 协同状态

这时候问题来了,如果用户按 Backspace 呢?浏览器默认行为可能会变成@User1->@Us。因为浏览器只知道:删除字符,但业务真正想要的是整个 mention 一起删除。

这时候你会第一次发现 DOM 根本不理解“这是一个 mention”,浏览器看到的是<span>@User1</span>,但编辑器真正关心的是:

{
  type: "mention",
  content: "@User1",
  data: {
    id: "user1"
  }
}

这里其实已经暴露了现代编辑器最核心的问题:DOM 无法表达稳定语义。

来看另一个特别真实的问题。比如用户点击Ctrl + B,浏览器可能生成:

<strong>hello</strong>

但有时候也可能生成:

<span style="font-weight:bold">
  hello
</span>

甚至:

<b>hello</b>

虽然视觉效果完全一样,但 DOM 结构已经不一样了,这时候问题就出现了。

编辑器真正想表达的其实是“这段文字是加粗”,而不是“用了哪种 HTML 标签”。

于是现代编辑器开始意识到 HTML 不是文档本身,HTML 只是展示结果。真正稳定的东西应该是:

{
  type: "text",
  content: "hello",
  styles: {
    bold: true
  }
}

这里语义终于开始和 DOM 分离。

2. 现代编辑器的设计

现代编辑器内部通常会维护一套 AST 抽象语法树,不过更重要的,是需要理解 AST 是编辑器自己的文档语言。

比如浏览器看到的是 <strong>hello</strong>,编辑器看到的是:

{
  type: "text",
  content: "hello",
  styles: {
    bold: true
  }
}

浏览器看到的是 <span data-id="user1">@User1</span>,编辑器看到的是:

{
  type: "mention",
  content: "@User1",
  data: {
    id: "user1"
  }
}

浏览器看到的是 <div><br></div>,编辑器看到的是:

{
  type: "paragraph",
  children: []
}

这里发生了一件特别重要的事情,编辑器开始拥有自己的“世界模型”。它终于不再只是操作 HTML,而是开始理解文档

现代编辑器真正维护的已经不是 DOM

这是现代编辑器最核心的一次变化。

  • 旧世界:DOM === Document
  • 新世界
    • EditorState === Document
    • DOM === Render Target

也就是说 DOM 不再是真相。真正的文档开始保存在:

  • AST
  • EditorState
  • Node Tree

这些内部状态里。DOM 只是最终渲染结果而已。

输入过程开始变成:DOM -> AST

来看一个最小实现。

比如:

<div id="editor" contenteditable="true"></div>

监听输入:

editor.addEventListener("input", handleInput);

然后:

function handleInput() {
  state.ast = parseHTML(editor.innerHTML);
}

这里发生的事情其实是:

  1. 浏览器先修改 DOM
  2. 编辑器再解析 DOM
  3. 转换成自己的文档状态

也就是说,DOM 只是输入层。真正保存的数据是 state.ast,这一步特别重要。因为编辑器终于开始“接管浏览器”。

Undo 为什么最终一定要自己实现?

浏览器其实已经有Ctrl + Z,但现代编辑器最后还是会自己维护:

  • undoStack
  • redoStack

为什么?

因为浏览器根本不知道什么叫“一次编辑操作”。比如 AI 改写需求,用户只点击了一次 AI 改写 按钮,但内部可能发生了:

  • 删除
  • 插入
  • mark 更新
  • selection 变化

如果依赖浏览器 undo,最后可能会变成一个字符一个字符撤销。但真正合理的行为应该是整个 AI 改写一次性回退。所以现代编辑器通常会这样:

undoStack.push(
  structuredClone(state.ast)
);

然后:

function undo() {
  state.ast = undoStack.pop();
  updateEditor();
}

这里的核心思想其实是撤销的不是 DOM。而是文档状态。

输入法问题

现代编辑器,通常还需要考虑输入法的问题。

很多人第一次做中文输入法时,编辑器会直接崩掉。因为中文输入并不是按键后立刻产生字符,而是:

n
↓
ni
↓
nih
↓
nihao
↓
你好

中间会经历:

  • compositionstart
  • compositionupdate
  • compositionend

如果你在拼音组合阶段,不停修改文档状态,那么编辑器可能就会出现:

  • 光标乱跳
  • 拼音中断
  • selection 丢失

因此现代编辑器都会维护一个关于是否 composition 的开关值:

state.isComposing = true;

然后:

if (state.isComposing) return;

等输入法真正结束后,再更新 AST。

3. 现代编辑器越来越像 React

讲到这里,你一定会发现,现代编辑器已经越来越像一个运行时,而非简单的 textarea 文本框。

这也是为什么很多人第一次看:

  • Lexical
  • ProseMirror
  • Slate

这些现代编辑器的源码时,都会有一种感觉:“怎么越来越像 React?”

因为它们其实都在做:

State
↓
Diff
↓
Reconcile
↓
Commit DOM

这和 React 非常像。

React 的世界

  1. State
  2. Virtual DOM
  3. Diff
  4. Update DOM

编辑器的世界

  1. EditorState
  2. Node Tree
  3. Reconcile
  4. Update DOM

你会发现,现代编辑器本质上已经变成状态驱动 UI 系统。DOM 不再是直接操作对象,而是渲染结果。

AI 出现之后,这种架构设计就更加显得重要了,无论是哪一种操作:

  • AI Rewrite Paragraph
  • AI Insert Table
  • AI Generate Block
  • AI Rewrite Section

AI 操作的都不是简单的字符,而是对应的文档结构。

4. 写在最后

回头再看整个编辑器的发展过程,你会发现整个过程就是:

  1. 浏览器原生编辑系统
  2. 逐渐失控
  3. 编辑器开始接管控制权
  4. 最终建立自己的文档世界

于是:

  • DOM 不再是真相
  • HTML 不再是文档
  • Selection 不再只是浏览器能力
  • Undo 不再交给浏览器
  • Input 不再完全相信 contenteditable

最后,现代富文本编辑器逐渐变成一套独立文档 runtime。内部的 AST 的真正意义也不仅仅是一种树结构,而是编辑器理解文档的方式。

这也是为什么现代编辑器最后会越来越像 React 的原因。

以上关于现代富文本编辑器为什么最终都会抛弃 DOM?的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » 现代富文本编辑器为什么最终都会抛弃 DOM?