实现一个富文本编辑器是一个复杂的项目,涉及到前端和后端的多个方面。以下是一个简化的实现方案,我们将使用HTML、CSS和JavaScript来构建一个基本的富文本编辑器。### 1. HTML结构首先,我们需要一个容器来放置编辑器:```htmlRich
摘要:在编辑器最开始的架构设计上,我们就以MVC模式为基础,分别实现模型层、核心层、视图层的分层结构。在先前我们讨论的主要是模型层以及核心层的设计,即数据模型以及编辑器的核心交互逻辑,在这里我们以React为例,讨论其作为视图层的模式扩展设计。
在编辑器最开始的架构设计上,我们就以MVC模式为基础,分别实现模型层、核心层、视图层的分层结构。在先前我们讨论的主要是模型层以及核心层的设计,即数据模型以及编辑器的核心交互逻辑,在这里我们以React为例,讨论其作为视图层的模式扩展设计。
开源地址: https://github.com/WindRunnerMax/BlockKit
在线编辑: https://windrunnermax.github.io/BlockKit/
项目笔记: https://github.com/WindRunnerMax/BlockKit/blob/master/NOTE.md
从零实现富文本编辑器系列文章
深感一无所长,准备试着从零开始写个富文本编辑器
从零实现富文本编辑器#2-基于MVC模式的编辑器架构设计
从零实现富文本编辑器#3-基于Delta的线性数据结构模型
从零实现富文本编辑器#4-浏览器选区模型的核心交互策略
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
从零实现富文本编辑器#6-浏览器选区与编辑器选区模型同步
从零实现富文本编辑器#7-基于组合事件的半受控输入模式
从零实现富文本编辑器#8-浏览器输入模式的非受控DOM行为
从零实现富文本编辑器#9-编辑器文本结构变更的受控处理
从零实现富文本编辑器#10-React视图层适配器的模式扩展
概述
多数编辑器实现了本身的视图层,而重新设计视图层需要面临渲染问题,诸如处理复杂的DOM更新、差量更新的成本,在业务上也无法复用组件生态,且存在新的学习成本。因此我们需要能够复用现有的视图层框架,例如React/Vue/Angular等,这就需要在最底层的架构上就实现可扩展视图层的核心模式。
复用现有的视图层框架也就意味着,在核心层设计上不感知任何类型的视图实现,针对诸如选区的实现也仅需要关注最基础的DOM操作。而这也就导致我们需要针对每种类型的框架都实现对应的视图层适配器,即使其本身并不会特别复杂,但也会是需要一定工作量的。
虽然独立设计视图层可以解决视图层适配成本问题,但相应的会增加维护成本以及包本身体积,因此在我们的编辑器设计上,我们还是选择复用现有的视图层框架。然而,即使复用视图层框架,适配富文本编辑器也并非是一件简单的事情,需要关注的点包括但不限于以下几部分:
视图层初始状态渲染: 生命周期同步、状态管理、渲染模式、DOM映射状态等。
内容编辑的增量更新: 不可变对象、增量渲染、Key值维护等。
渲染事件与节点检查: 脏DOM检查、选区更新、渲染Hook等。
编辑节点的组件预设: 零宽字符、Embed节点、Void节点等。
非编辑节点内容渲染: 占位节点、只读模式、插件模式、外部节点挂载等。
此外,基于React实现视图层适配器,相当于重新深入学习React的渲染机制。例如使用memo来避免不必要的重渲染、使用useLayoutEffect来控制DOM渲染更新时机、严格控制父子组件事件流以及副作用执行顺序等、处理脏DOM的受控更新等。
除了这些与React相关的实现,还有一些样式有关的问题需要注意。例如在HTML默认连续的空白字符,包括空格、制表符和换行符等,在渲染时会被合并为一个空格,这样就会导致输入的多个空格在渲染时只显示一个空格。
为了解决这个问题,有些时候我们可以直接使用HTML实体 来表示这些字符来避免渲染合并,然而我们其实可以用更简单的方式来处理,即使用CSS来控制空白符的渲染。下面的几个样式分别控制了不同的渲染行为:
whiteSpace: "pre-wrap": 表示在渲染时保留换行符以及连续的空白字符,但是会对长单词进行换行。
wordBreak: "break-word": 可以防止长单词或URL溢出容器,会在单词内部分行,对中英文混合内容特别有用。
overflowWrap: "break-word": 同样是处理溢出文本的换行,但自适应宽度时不考虑长单词折断,属于当前标准写法。
<div
style={{
whiteSpace: "pre-wrap",
wordBreak: "break-word",
overflowWrap: "break-word",
}}
></div>
这其中word-break是早期WebKit浏览器的实现,给word-break添加了一个非标准的属性值break-word。这里存在问题是,CSS在布局计算中,有一个环节叫Intrinsic Size即固有尺寸计算,此时如果给容器设置width: min-content时,容器就坍塌成了一个字母的宽度。
