背景
某天,项目里需要用到一个简单的富文本,我本着代码体积最小化原则,决定自己实现这个富文本。
execCommand
execCommand是实现富文本的神器之一。它可以实现文字加粗等效果,然而它有很多小缺点,例如:
- 无法自定义插入的标签
- 某种特殊情况下,它插入标签前会改变原来的DOM结构,例如span标签被删除了、多个span标签被合并了
- 没有返回插入的DOM元素,需亲自遍历获取,前提是知道哪些DOM元素是新插入的
无奈之下,只好自己动手实现一个execCommand,即选中一段文本再插入自定义标签的效果。
从此坠落深坑……
surroundContents
查了下资料,找到surroundContents,so easy!
# html
<body>
<p>我是一段文本,我是一段文本<p>
<button>click</button>
</body>
# JavaScript
document.querySelector('button').onclick = () => {
const selection = window.getSelection()
const range = selection.getRangeAt(0)
range.surroundContents(document.createElement("span"))
}
坠落过程中,我抓住了一根树枝,稳住了身姿,欣喜若狂!
就在我想借力往上爬时,这根救命稻草居然断了……
在选择跨标签文本时,即光标起点在第一个<p>,光标结点在第二个<p>,surroundContents居然报错了!?
# html
<body>
<p>我是一段文本,我是一段文本<p>
<p>我是另一段文本,我是另一段文本<p>
<button>click</button>
</body>
# JavaScript
document.querySelector('button').onclick = () => {
const selection = window.getSelection()
const range = selection.getRangeAt(0)
range.surroundContents(document.createElement("span"))
}
# error
Uncaught DOMException: Failed to execute 'surroundContents' on 'Range': The Range has partially selected a non-Text node.
坠到深坑底部……万念俱灰!
surroundCrossContents
搜索了一阵子,国内外都没有关于跨标签的解决方案。再次无奈,只好自己动手……
surroundCrossContents实现思路:
- Range会返回起始节点和结束节点,以及这两个节点的共同父节点
- 首先,遍历父节点,找到起始节点和结束节点之间的所有兄弟节点
- 接着,一顿DOM操作,用自定义标签包裹这些节点
详细代码请查看github
至此,我终于从深坑里爬出来了!