# 富文本基础概念篇

Jason
Table of Contents

常用富文本编辑器梳理

编辑器名称网址主要介绍
TinyMCEhttps://www.tiny.cloud/易用、功能强大、所见即所得、开源可商用
CKEditorhttps://ckeditor.com/开箱即用
UEEditorhttps://github.com/fex-team/ueditor百度 FEX-team
AI Editorhttps://aieditor.dev/zh/ai集成, 简单友好
slatejshttps://www.slatejs.org/examples/richtext完全可定制
Lexicalhttps://lexical.dev/扁平化数据管理
quillhttps://quilljs.com/
Editor-kithttps://editor-kit.web.bytedance.net/docs/quick-start飞书富文本
Tiptaphttps://tiptap.dev/

JS富文本常见API

Contenteditable

MDN:https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/contenteditable

基本功能:表示元素是否可被用户编辑,是实现编辑器的最基本属性

可选属性值:

  • true 或空字符串,表示元素是可编辑的。
  • false 表示元素不是可编辑的。
  • plaintext-only 表示元素的原始文本是可编辑的,但富文本格式会被禁用。

execCommand(deprecated)

MDN 文档中介绍,execCommand是一种为富文本编辑设计的API。它可以在网页文档中执行各种文本操作命令,让开发者轻松实现加粗、斜体、复制和插入链接等功能。

这个API曾经非常流行,但随着网络技术的发展,它逐渐被淘汰,现在MDN已将其标记为⚠️废弃,不再建议使用。

不再使用的原因:

  • 兼容性问题:现代浏览器对execCommand的支持越来越少,不同浏览器的表现也不一致。
  • 功能有限:无法满足复杂的格式和个性化需求

替代方法:通过直接操作SelectionRange对DOM进行控制,以实现自定义的文本编辑功能

Selection

1. 关键属性

属性说明
anchorNode选中的起始节点(鼠标按下时的节点)
anchorOffset起始节点内的偏移量(比如文本节点的字符位置,元素节点的子节点索引)
focusNode选中的结束节点(鼠标松开时的节点)
focusOffset结束节点内的偏移量
isCollapsed是否「折叠」(即没有选中任何内容,只有光标)
rangeCount包含的 Range 数量

相关术语

  • 锚点(anchor):锚点指的是一个选区的起始点。当鼠标框选某个区域时,鼠标点击的那一瞬间锚点的位置即确定了,拖动时不会变化。
  • 焦点(focus):选区的焦点是选区的终点,当鼠标框选某个区域时,鼠标松开的一瞬间焦点即确定了,焦点位置随拖动变化。
  • 范围(range):范围指文档中连续的一部分。一个范围可能包含整个节点,也可能只包含某个节点的一部分。用户一般只能选择一个返回,但是也有可能会选择多个范围,例如使用Control+Click选择多个区域(Chrome 禁止了这个操作)。「范围」会被作为 Range 对象返回。

2. 常用方法

  • selection.collapse(node, offset):将选中折叠到某个位置(设置光标);例:把光标移到段落第 5 个字符后:

    const p = document.querySelector('p');
    selection.collapse(p.firstChild, 5); // p.firstChild 是文本节点
  • selection.selectAllChildren(node):选中某个元素的所有子节点;例:选中 div 内的所有内容:

    const div = document.querySelector('div');
    selection.selectAllChildren(div);
  • selection.deleteFromDocument():删除所有选中的内容;例:删除用户选中的文本:

    selection.deleteFromDocument();

Range

  1. 创建Range

通过 document.createRange() 创建空 Range,再用方法定义范围:

const range = document.createRange();

  1. 常用方法:定义范围

range.setStart(node, offset) + range.setEnd(node, offset)

:精准定义范围;

例:选中文本节点的第 0 到 5 个字符:

const textNode = document.querySelector('p').firstChild;
range.setStart(textNode, 0); // 从第0个字符开始
range.setEnd(textNode, 5); // 到第5个字符结束

range.selectNode(node):选中整个节点(包括节点本身);

const btn = document.querySelector('button');
range.selectNode(btn);

range.selectNodeContents(node):选中节点的所有子内容(不包括节点本身);例:选中 div 内的文本(不包括 div 标签):

const div = document.querySelector('div');
range.selectNodeContents(div);

  1. 常用方法:操作范围内容

这部分是实战重点,比如「加粗选中文本」「插入内容到光标」都依赖这些方法:

方法说明
range.surroundContents(wrapper)用 wrapper 包裹范围内容(要求范围不跨节点边界,否则报错)
range.extractContents()提取范围内容为 DocumentFragment(从 DOM 中移除原内容)
range.deleteContents()删除范围内容(不返回)
range.insertNode(node)在范围起始位置插入节点
range.cloneRange()复制 Range(修改副本不影响原 Range)

示例代码

  1. 例子一:把选中的文本「加粗」(富文本核心操作)
function boldSelectedText() {
const selection = window.getSelection();
if (selection.isCollapsed) return; // 没有选中内容,直接返回
const range = selection.getRangeAt(0);
const bold = document.createElement('b');
try {
// 尝试直接包裹(要求范围不跨节点)
range.surroundContents(bold);
} catch (e) {
// 跨节点时,提取内容再包裹(比如选了一个 span 的一部分)
const fragment = range.extractContents();
bold.appendChild(fragment);
range.insertNode(bold);
}
// 保持选中状态(否则加粗后选中会消失)
selection.removeAllRanges();
const newRange = document.createRange();
newRange.selectNodeContents(bold);
selection.addRange(newRange);
}
// 绑定按钮点击事件
document.querySelector('#bold-btn').addEventListener('click', boldSelectedText);
  1. 例子二:在光标位置插入文本(比如「@提及」功能)
function insertTextAtCursor(text) {
const selection = window.getSelection();
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
const textNode = document.createTextNode(text);
if (range.collapsed) {
// 光标状态:直接插入到光标位置
range.insertNode(textNode);
// 移动光标到插入文本之后
range.setStartAfter(textNode);
range.collapse(true);
} else {
// 选中状态:替换选中内容
range.deleteContents();
range.insertNode(textNode);
range.collapse(false); // 光标移到文本末尾
}
// 更新 Selection
selection.removeAllRanges();
selection.addRange(range);
}
// 调用:在光标位置插入 "@test"
insertTextAtCursor('@test');

注意事项

  1. DOM 修改后,Range 会失效:如果删除了 Range 中的节点(比如 range.startContainer),原 Range 会变成「无效状态」,需要重新获取。
  2. surroundContents 的限制:该方法要求 Range 不能「部分选中」节点(比如选了一个 <span> 的前半部分和 <div> 的后半部分),否则会抛出错误。此时用 extractContents() + insertNode() 替代。
  3. 兼容性

    • 现代浏览器(Chrome/Edge/Firefox/Safari)完全支持;
    • IE 浏览器用 document.selection(已淘汰,无需兼容)。
  4. Selection 是「实时的」:用户改变选中内容时,window.getSelection() 返回的对象会自动更新,不需要重新获取。
My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts