需求描述
用户在一段文本输入过程中,如果遇到系统内存在的姓名就用蓝色标识出来,也可以在光标处通过点击姓名的方式把姓名加进去。
交互细节,拆分
- 中括号文本内容,匹配到姓名,颜色标蓝色,不符合条件则不标记颜色
- 在鼠标焦点位置,可以粘贴文本
- 在鼠标焦点位置,可以点击姓名插入
- 复制粘贴姓名后,蓝色样式还在
首先了解下 window.getSelection() 是什么 ?
它返回一个 Selection
对象,表示用户选择的文本范围或光标的当前位置。
如果一个用户光标在这里:
此时可编辑 div 内部有四个子节点
此时获取 window.getSelection()
返回的结果是:
可以看到现在焦点所在的位置是第二个文本节点内(展开 anchorNode
),#text
类型,位置在第二个文本节点的第二个元素后面,因为 anchorOffset: 2
。
方案步骤描述(onInput)
初期方案是,维护各自的块内容,比如如果用户操作的是第二个 text 内容块,那么就根据当前块的内容和位移,进行格式化当前文本,这样焦点不会有问题。但是如果用户跨 childNode 如果形成了新的内容,复制或者粘贴半个块,就会比较难处理了,需要控制的交互边界比较多,所以这里采用了现在的方案。
- 监听 div 的 onInput 事件,每次变动后获取最新的文本
e.target.innerText
- 记录此时光标在纯文本的位置,比如上面的例子,记录的位置应该是
6
- 此时将文本格式化,对的名称用 font 包裹
python
# before
[张三]去了一趟理塘,[李四]去了一趟丽江。
# after, 用 font 包裹
<font color="#1E90FF">[张三]</font>去了一趟理塘,<font color="#1E90FF">[李四]</font>去了一趟丽江。
- 将格式化后的内容重新塞到 div 的 innerHTML 中,dom 重绘,获取新的
childNodes
; - 根据记录的焦点位置,对应到新的 nodes 上面,并且存为新的焦点,设置到 selection 上
js
// 将光标塞到计算出的新的位置
const selection = window.getSelection();
selection.removeAllRanges(); // 将现在的 selection 选区中光标的选择范围全部清除
const range = document.createRange(); // 生成一个光标选择范围
range.selectNodeContents(divRef.current); // 将选区设置到当前的 div
range.collapse(true); // 折叠选取,光标处于开始位置
range.setEnd(newAnchorNode, newAnchorOffset); // 设置光标结束点,到选区中的某个 node 的 offset 位置
range.setStart(newAnchorNode, newAnchorOffset); // 设置开始点
selection.addRange(range); // 将创建的新的光标 塞入当前的 selection 中
- 此时焦点正确,内容也格式化完成。
点击插入名称的实现
- 在 div
onBlur
的时候记录下当时的焦点信息,位置,比如6
- 点击时候,获取到焦点信息,没有就取末尾
- 将当前 div 内部的文本按照焦点位置分割,分为开头和结尾
- 将姓名插入形成新的文本,比如在张三后面插入李四,新文本是
[张三][李四]去了一趟理塘,[李四]去了一趟丽江。
- 重新格式化上述文本,之后塞到 div 的 innerHTML 里面,失焦处理