news 2026/3/4 8:08:19

富文本编辑基础核心

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
富文本编辑基础核心

富文本开发核心

富文本编辑器的开发核心在于支持多样化的文本操作和功能,包括:

  • 文本格式化:加粗,斜体,下划线,颜色,背景色等多种文本样式。
  • 多媒体插入:图片,视频,文本,连接等。
  • 撤销重做:用户在编辑过程中随时撤销和重做操作。

contentenditable基础

  • contenteditle属性:

    • 是HTML属性,可以使任何元素变为可编辑状态。通过设置contentEditable="true",用户可以直接在元素内输入或删除文本。
    • 常用于构建富文本编辑器的编辑区域,简单高效。

创建一个编辑的div元素。

<div id="editor-content" contentEditable="true"> <p> 这里是一个可编辑层 </p> </div>

创建多个可编辑区域

<div id="editor-content" contentEditable="true"> <div contentEditable="true"> <div contentEditable="true">1</div> <div contentEditable="true">2</div> <div contentEditable="true">3</div> </div> </div>

文字加粗和斜体

<div contentEditable="true"> <div contentEditable="true">1</div> <div contentEditable="true">2</div> <div contentEditable="true">3</div> <i>斜体<span>元素</span></i> <div contentEditable="true">4</div> <b>加粗</b> </div>

HTML字符串作为富文本存储的性能问题

  1. 析与序列化开销大。
  2. 存储效率低
  3. 修改与操作困难
  4. Diff与合并性能差

使用JSON Context

JSON Context是结构化数据表示(通常为树状JSON对象),核心是用“节点类型+属性+子节点”的模型抽象富文本内容,完美解决HTML的性能痛点

定义(上面HTML描述):

constdoc={type:'doc',content:[{type:'paragraph',content:[{type:'text',text:'1'}]},{type:'paragraph',content:[{type:'text',text:'2'}]},{type:'paragraph',content:[{type:'text',text:'3'}]},{type:'paragraph',content:[{type:'text',text:'斜体',marks:[{type:'italic'}]},{type:'text',text:'元素',marks:[{type:'italic'}]}]},{type:'paragraph',content:[{type:'text',text:'3'}]},{type:'paragraph',content:[{type:'text',text:'加粗',marks:[{type:'bold'}]}]}]};

给富文本添加 onInput 事件

consthandleInput=(e:InputEvent)=>{consttarget=e.currentTargetasHTMLElement;console.log(target.innerHTML)// DOMParser 是浏览器原生 API,用于将字符串解析为 完整的 HTML 文档对象(Document)。constdomParser=newDOMParser();constdoc=domParser.parseFromString(target.innerHTML,"text/html");console.log(target.innerHTML,'target.innerHTML')console.log(doc.body.childNodes,'doc')}

将html dom 转换为AST

typeTextStyle={bold?:boolean;// 加粗italic?:boolean;// 斜体underline?:boolean;// 下划线};/** * 关于AST 节点定义 */typeASTNode={//type:"text"|"paragraph"|"mention"|"custom"|'heading';content?:string;style?:TextStyle;attrs?:any;children?:ASTNode[];data?:any;};/** * @param html html字符串 * 将html dom 转换为AST */exportfunctionparseHtml(html:string){// DOMParser 是浏览器原生 API,用于将字符串解析为 完整的 HTML 文档对象(Document)。constparse=newDOMParser()constdoc=parse.parseFromString(html,'text/html')// nodes doc.body 的所有直接子节点constnodes=doc.body.childNodesconsole.log(nodes,'doc')/** * * @param node 节点 * 遍历每一个node,将 dom node 转换为 AST * */functionparseNode(node:Node):ASTNode|null{console.log(node)// 如果是文本节点if(node.nodeType===Node.TEXT_NODE){return{type:"text",content:node.textContent||'',// 文本节点内容style:{},// 样式data:{}}}// 如果节点是元素节点if(node.nodeType===Node.ELEMENT_NODE){constelement=nodeasHTMLElement// 小写的元素名称consttagName=element.tagName.toLowerCase()// 获取元素属性constdata=element.dataset// 获取元素样式conststyle=element.styleconsole.log(data,style)// 判断不同元素节点,返回不同的AST节点switch(tagName){case"h1":return{type:'heading',attrs:{level:1},content:element.textContent||'',}case"h2":return{type:'heading',attrs:{level:2},content:element.textContent||'',}case"p":return{type:"paragraph",content:element.textContent||'',}default:break}}returnnull}returnArray.from(doc.body.childNodes).map(parseNode)}

鼠标选中文字通过按钮添加文字样式

在这里插入图片描述

添加事件

<div> <button onClick={()=>format('bold')}>加粗</button> <button onClick={()=>format('italic')}>斜体</button> <button>@</button> <div contentEditable="true" onInput={handleInput}> <h1>我是H1</h1> <h2>我是H2</h2> <p>元素</p> 444 </div> </div>

实现逻辑

const format = (commod: 'bold' | 'italic') => { // 1. 获取当前页面的 Selection 对象(表示用户选中的文本范围或光标位置) const selection = window.getSelection(); // 如果没有选区(例如在非浏览器环境或选区为空),直接退出 if (!selection) return; // 2. 获取选区中的第一个 Range(通常只有一个,除非跨多区域选择) // Range 表示文档中连续的一段内容(可跨节点) const range = selection.getRangeAt(0); console.log(range, 'range'); // 用于调试:查看当前选区的 Range 对象 // 3. 创建一个新元素来包裹格式化后的文本 // - 如果是 'bold',创建 <b> 标签;如果是 'italic',创建 <i> 标签 const formattedText = document.createElement(commod === 'bold' ? 'b' : 'i'); // 将选中的纯文本内容设置为新元素的文本内容 // ⚠️ 注意:这会丢失原有 HTML 结构(如嵌套标签)! formattedText.textContent = range.toString(); // 4. 删除原选区中的内容(包括文本和任何内联元素) range.deleteContents(); // 5. 将新创建的格式化元素插入到原选区位置 range.insertNode(formattedText); // ✅ 可选增强:将光标移动到格式化文本之后,提升用户体验 // selection.removeAllRanges(); // const newRange = document.createRange(); // newRange.setStartAfter(formattedText); // newRange.collapse(true); // selection.addRange(newRange); // 富文本编辑器不存在受控组件 };

Selection+Range+Compiler
ange接口

Range表示文档中连续的一段内容(可跨节点),是操作 DOM 的底层单位。

常用属性

属性说明
startContainer/startOffset起始容器节点和偏移
endContainer/endOffset结束容器节点和偏移
commonAncestorContainer起止节点的最近公共祖先

常用方法

方法说明
cloneContents()克隆选中内容(返回 DocumentFragment)
extractContents()提取并删除选中内容
deleteContents()仅删除选中内容
insertNode(node)在 Range 起始处插入节点
surroundContents(newNode)用新节点包裹 Range 内容(要求 Range 是“可包裹”的)
selectNode(node)/selectNodeContents(node)选中整个节点或其内容
setStart(node, offset)/setEnd(...)手动设置边界

Selection 接口

Selection对象表示用户或脚本在文档中选中的文本范围。它通常与光标或鼠标选择操作相关联。

Selection表示用户在页面中选中的文本范围(或光标位置)。可通过window.getSelection()获取。

常用属性

属性说明
anchorNode/anchorOffset选择起点的节点和偏移
focusNode/focusOffset选择终点的节点和偏移
isCollapsed是否为光标(无选中文本)
rangeCount包含的 Range 数量(通常为1)

常用方法

方法说明
getRangeAt(0)获取当前选区的 Range 对象
removeAllRanges()清除所有选区
addRange(range)添加一个 Range 到选区
collapse(node, offset)将选区折叠为光标
deleteFromDocument()删除选中的内容

富文本内容更新

通常文本内容比较多。如果每次更新数据,用全量更新导致页面卡顿。这个时候就只能对比新旧AST树来增量更新。
fast-json-patch是一个高性能的 JavaScript 库,用于生成和应用 JSON Patch(RFC 6902)操作,常用于:

  • 状态同步(如富文本协作编辑、实时协同)
  • 撤销/重做(Undo/Redo)
  • 高效传输数据变更(只传 diff,不传全量)

安装

npminstallfast-json-patch
核心功能
1.生成 Patch(比较两个 JSON 对象)
import{compare}from'fast-json-patch';constobj1={name:"Alice",age:30};constobj2={name:"Alice",age:31,city:"Beijing"};constpatch=compare(obj1,obj2);console.log(patch);// 输出:// [// { op: "replace", path: "/age", value: 31 },// { op: "add", path: "/city", value: "Beijing" }// ]

compare(a, b)返回将a变成b所需的操作序列。

2.应用 Patch(将操作应用到目标对象)
import{applyPatch}from'fast-json-patch';constdoc={title:"Hello"};constpatch=[{op:"replace",path:"/title",value:"Hi"}];constresult=applyPatch(doc,patch,true);// 第三个参数:是否 mutate 原对象console.log(result.newDocument);// { title: "Hi" }

⚠️ 默认applyPatch不会修改原对象(返回新对象)。
若设mutate: true(第三个参数为true),则直接修改原对象。


3.创建可观察对象(自动记录变更)
import{observe,generate}from'fast-json-patch';constdoc={count:0};constobserver=observe(doc);// 开启监听doc.count=1;doc.flag=true;constpatches=generate(observer);// 获取自 observe 以来的所有变更console.log(patches);// [// { op: "replace", path: "/count", value: 1 },// { op: "add", path: "/flag", value: true }// ]

历史记录栈与Undo/Redo

点击撤销

import {compare} from "fast-json-patch" const [ast, setAST] = useState<ASTNode[]>([]); // 当前 AST 状态 const [undoStack, setUndoStack] = useState<ASTNode[][]>([]); // 撤销栈 const [redoStack, setRedoStack] = useState<ASTNode[][]>([]); // 重做栈 const handleInput = () => { if (!editorRef.current) return; const html = editorRef.current.innerHTML; const newAST = parseHTML(html); // 🔄 生成 patch:从旧 AST 到新 AST 的变更 const patches = compare(ast, newAST); console.log('JSON Patch:', patches); // 💾 保存当前状态到撤销栈 setUndoStack((prevStack) => [...prevStack, ast]); // 🚫 清空重做栈(因为重新开始) setRedoStack([]); // 🔁 更新 AST setAST(newAST); }; // ⏪ 撤销操作 const undo = () => { if (undoStack.length === 0) return; const prevAST = undoStack[undoStack.length - 1]; // 将当前状态推入重做栈 setRedoStack(prev => [...prev, ast]); // 设置回上一个状态 setAST(prevAST); setUndoStack((prevStacl)=> prevStacl.slice(0, -1)); }; // ⏩ 重做操作 const redo = () => { if (redoStack.length === 0) return; const nextAST = redoStack[redoStack.length - 1]; const newRedoStack = redoStack.slice(0, -1); // 推入撤销栈 setUndoStack(prev => [...prev, ast]); // 应用重做状态 setAST(nextAST); setRedoStack(newRedoStack); };
  1. 每操作一步,把当前状态推入撤销栈。
  2. 点击撤销:取撤销栈最后一步 → 设为当前状态 → 删除撤销栈最后一步 → 当前原状态推入重做栈。
  3. 点击重做:取重做栈最后一步 → 设为当前状态 → 删除重做栈最后一步 → 当前原状态推入撤销栈。

Tiptap

Tiptap,一款为开发富文本编辑器为生的框架

Tiptap 是一个基于ProseMirror的现代化、可扩展的React/Vue/原生 JavaScript 富文本编辑器框架,具有无头(headless)设计TypeScript 支持协作编辑能力等优势

安装依赖
# 核心包(必须)npminstall@tiptap/react @tiptap/pm# 常用扩展(按需安装) 包含:段落、标题、加粗、斜体、列表、链接、历史记录等常用功能。npminstall@tiptap/starter-kit
基础使用
import { useEditor, EditorContent } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import React,{useState, useEffect, useRef} from "react"; export default function RichTextEditor() { const editor = useEditor({ extensions: [StarterKit], content: '<p>开始编辑...</p>', }); if (!editor) return null; return ( <div> <div style={{ marginBottom: '10px', padding: '8px', border: '1px solid #ccc' }}> <button onClick={() => editor.chain().focus().toggleBold().run()}> <strong>B</strong> </button> <button onClick={() => editor.chain().focus().toggleItalic().run()}> <em>I</em> </button> <button onClick={() => editor.chain().focus().toggleBulletList().run()}> • </button> <button onClick={() => editor.chain().focus().undo().run()}>↩️ Undo</button> <button onClick={() => editor.chain().focus().redo().run()}>↪️ Redo</button> </div> <EditorContent editor={editor} /> </div> ); }

功能
协作编辑@tiptap/extension-collaboration+ WebSocket
图片上传@tiptap/extension-image
表格@tiptap/extension-table
代码块@tiptap/extension-code-block
字体颜色@tiptap/extension-text-style+@tiptap/extension-color
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/28 0:08:08

2026爆火9款免费AI论文工具:自动降重+高级表达一键替换!

最后72小时&#xff01; 毕业季倒计时&#xff0c;论文死线逼近&#xff0c;科研申报进入冲刺期——如果你还在为“写不完、改不动、降重难”彻夜焦虑&#xff0c;那么这篇2026最新急救指南就是你的唯一捷径。本文将用最快的速度告诉你&#xff1a;哪一款AI论文工具能在24小时内…

作者头像 李华
网站建设 2026/2/27 1:44:31

1小时打造你的专属解压工具原型

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 快速开发一个解压软件最小可行产品(MVP)&#xff0c;包含核心功能&#xff1a;1)文件拖拽解压 2)基础格式支持(ZIP,RAR) 3)解压进度显示 4)简单设置界面。使用PythonPyQt5实现&…

作者头像 李华
网站建设 2026/3/3 14:00:20

3分钟极速安装Docker Desktop的秘诀

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个Docker Desktop极速安装工具&#xff0c;功能要求&#xff1a;1.使用国内镜像源加速下载 2.并行执行下载和系统检查任务 3.预配置常用设置(镜像加速等) 4.最小化用户交互步…

作者头像 李华
网站建设 2026/3/1 5:07:08

30分钟打造‘REBOOT‘错误诊断工具原型

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 快速开发一个最小功能的启动错误诊断工具原型&#xff0c;要求&#xff1a;1. 基本硬盘检测功能 2. 简单BIOS设置检查 3. 基础修复建议 4. 简洁的CLI界面 5. 可在30分钟内完成开发…

作者头像 李华
网站建设 2026/3/3 9:05:19

Java员工岗前培训学习平台vue3

目录Java员工岗前培训学习平台&#xff08;Vue3&#xff09;摘要开发技术核心代码参考示例1.建立用户稀疏矩阵&#xff0c;用于用户相似度计算【相似度矩阵】2.计算目标用户与其他用户的相似度总结源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&am…

作者头像 李华