如何开发富文本编辑器框架(二)——了解选区

3,384 阅读3分钟

目录

正文

上次介绍了 Leaf、Text、Mark 模型。今天介绍 Point、Range、Selection 模型。

Point

interface PointInterface {
    key: string;
    offset: number;
}

Point 的意思。一个 Range(范围) 由2个 Point组成,即起点和终点。Pointkey是某个Text节点的key,这个key是在单独的作用域里维护的,是自增的,每创建一个 Node 节点就会产生一个 key赋予 Node节点。

generate-key.ts

/**
 * 自增索引
 * @type {Number}
 */
let n: number;

/**
 * 全局的key生成函数
 *
 * @type {Function}
 */
let generate: () => string;

/**
 * 生成一个key
 *
 * @return {String}
 */
function generateKey(): string {
    return generate();
}

/**
 *  自定义唯一key生成函数
 *
 * @param {Function} func
 */
function setKeyGenerator(func): void {
    generate = func;
}

/**
 * 重置key
 */
function resetKeyGenerator(): void {
    n = 0;
    generate = () => `${n++}`;
}

/**
 * 初始化
 */
resetKeyGenerator();

export default generateKey;
export { setKeyGenerator, resetKeyGenerator };

创建一个 Text 节点

new Text({
    key: generateKey(),
    leaves: []
})

Pointoffset可以理解相对于 text 节点的位置偏移量。上篇文章里,我举了个 markdown 的例子。

有的文字加粗,有的斜体。

这段文本对应的结构是

Text({
    key: 1,
    leaves: [
        Leaf({
            text: '有的文字',
            marks: []
        }),
        Leaf({
            text: '加粗',
            marks: ['bold']
        }),
        Leaf({
            text: ',有的',
            marks: []
        }),
        Leaf({
            text: '斜体',
            marks: ['italics']
        }),
    ]
})

此时,有同学产生了一个需求,需要把 加粗 前的 文字 也加粗。那我们该怎么定这个位置?

Range 、Selection

注意:Range 是范围,Selection 是选区,范围没有方向,选区才有方向。从前往后拖选文字,和从后往前拖选文字,selection 不同,但 range 是相同的。

interface RangeInterface {
    start: PointInterface, // 起点
    end: PointInterface // 终点
}

interface SelectionInterface {
    anchor: PointInterface; // 光标开始的点
    focus: PointInterface; // 光标结束的点
}

如果是从后往前拖选,anchor 对应的dom节点在后,focus 对应的节点在前面。

通常来说,一个 selection 只会对应一个 range

Selection API 最初由 Netscape 创建,并允许多个区域(例如,允许用户从 <table> 中选择列)。但是,Gecko以外的浏览器没有实现多个区域,而且规范还要求选择的内容始终(仅)具有一个范围(允许多个区域可能引起不必要的兼容性问题,例如同时从多处输入)。

回到我们的例子,加粗 两个字的范围是 key = 1, offset 4 ~ 6;文字 的范围是 offset 2~ 4。

我们手动选中了 文字,这是一个浏览器的原生操作,但是怎么告诉框架你选中了数据上的这个 文字。事实上,框架会监听 onSelect 事件,在事件回调函数里调用原生方法 window.getSelection() 获取起始点的dom信息来更新自己维护的 selection。(参阅 Selection mdn

const window = getWindow(event.target);
const { value } = change;
const { document, schema } = value;
const native = window.getSelection(); // 原生选区

// 如果没有范围,slate 自动失去焦点
if (!native.rangeCount) {
  change.blur();
  return;
}

// 将原生的range 转换成 slate 的 range
let range = findRange(native, value);
if (!range) return;
...
...
 // 创建 Slate 的选区
let selection = document.createSelection(range);
selection = selection.setIsFocused(true);

自此文字选中了,框架从它的数据上也知道你选中了哪个范围。接下来我们就要开始加粗文字了。

由于, Slate 是基于受控的 contennteditable 实现的,所以所有会破坏dom结构的事件都会被它拦截掉。SlateonKeyDown 中拦截默认行为,我们根据之前产生的 selectionanchorfocus , 找到对应的 Text 节点,接下来就是操作数据(大家都会的对象、数组操作),最后把新的 state 交给 Slate 来更新渲染。

选区的内容,差不多就讲到这里了,如果哪里写的不对或写的不够详尽的,可以在评论里指出,我会修改的。

Resousces