实现一个富文本编辑器是一个复杂的项目,涉及到前端和后端的多个方面。以下是一个简化的实现方案,我们将使用HTML、CSS和JavaScript来构建一个基本的富文本编辑器。### 1. HTML结构首先,我们需要一个容器来放置编辑器:```htmlRich
摘要:在先前我们基于Range对象与Selection对象实现了基本的浏览器选区操作,并且基于编辑器数据模型设计了RawRange和Range对象两种选区模型。在这里我们需要将浏览器选区与编辑器选区关联起来,以此来确认应用变更时的操作区间,相当于
在先前我们基于Range对象与Selection对象实现了基本的浏览器选区操作,并且基于编辑器数据模型设计了RawRange和Range对象两种选区模型。在这里我们需要将浏览器选区与编辑器选区关联起来,以此来确认应用变更时的操作区间,相当于我们需要基于DOM实现受控的选区同步。
开源地址: 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-浏览器选区与编辑器选区模型同步
描述
当前的主要目标是将浏览器选区与编辑器选区模型同步,也就是希望实现受控的DOM选区同步。实际上这里需要考虑的问题非常多,例如DOM节点是非常复杂的,特别是在支持插件化的渲染模式下,如何将其归一化,以及如何处理ContentEditable的受控渲染问题等等。
我们先来处理最简单的选区同步问题,也就是纯文本节点的选区Case。先来回顾一下浏览器中纯文本的选区操作,下面的例子中,我们就可以获取文本片段23的位置,这里的firstChild是Text节点,即值为Node.TEXT_NODE类型,这样才可以计算文本内容的片段。
<span id="$1">123456</span>
<script>
const range = document.createRange();
range.setStart($1.firstChild, 1);
range.setEnd($1.firstChild, 3);
console.log(range.getBoundingClientRect());
</script>
在编辑器选区模型中,我们定义了Range对象以及RawRange对象来表示编辑器选区状态。RawRange对象的设计与Quill编辑器的选区设计保持一致,毕竟通常来说选区设计的直接依赖便是数据结构的设计,RawPoint对象则直接维护了起始偏移的值。
export class RawPoint {
constructor(
/** 起始偏移 */
public offset: number
) {}
}
export class RawRange {
constructor(
/** 起始点 */
public start: number,
/** 长度 */
public len: number
) {}
}
Range对象选区的设计直接基于编辑器状态的实现,基于Point对象维护了行索引和行内偏移,Range对象则维护了选区的起始点和结束点。此时的Range对象中区间永远是从start指向end,通过isBackward来标记此时是否反选状态。
export class Point {
constructor(
/** 行索引 */
public line: number,
/** 行内偏移 */
public offset: number
) {}
}
export class Range {
/** 选区起始点 */
public readonly start: Point;
/** 选区结束点 */
public readonly end: Point;
/** 选区方向反选 */
public isBackward: boolean;
/** 选区折叠状态 */
public isCollapsed: boolean;
}
实际上这里进行选区同步的主要目标是我们希望借助ContentEditable实现内容的输入,以及借助浏览器原本的选区模型来实现文本选择效果,而不是额外维护input实现输入以及自绘选区来实现文本选择效果。因此我们借助了更多浏览器能力,则需要大量逻辑来实现受控的模式同步。
而在整个流程中,我们需要完成双向的转换。当浏览器选区发生变化的时候,我们需要获取最新的DOM选区,并将其转换为Model选区。
