使用 contenteditable=”true” 制作简单富文本编辑器常见问题

项目中可能会遇到这样的简单需求,就是要点击相关的按钮在文本编辑器的光标处点插入相应的纯文本(不带格式粘贴);同时文本编辑器要求有不带格式粘贴的功能。
涉及到的知识是对光标的处理、剪贴板处理相关的知识。

简单富文本编辑器界面如下:

简单富文本编辑器界面

代码如下:

            const $input = document.getElementById('my-input')
            const $btns = [...document.querySelectorAll('.my-btn')]
            let lastEditRange
            // 重置 placeholder
            function resetPlaceholder () {
                if ($input.innerText.length > 0) {
                    $input.classList.remove('placeholder-visible')
                } else {
                    $input.classList.add('placeholder-visible')
                }
            }
            resetPlaceholder()
            // 点击事件
            $input.onclick = function () {
                // 获取选定对象
                // 设置最后光标对象
                lastEditRange = getSelection().getRangeAt(0)
            }
            // 编辑框按键弹起事件
            $input.onkeyup = function () {
                // 获取选定对象
                // 设置最后光标对象
                lastEditRange = getSelection().getRangeAt(0)
                resetPlaceholder()
            }
            $input.onpaste = function (event) {
                const e = event || window.event
                // 阻止默认粘贴
                e.preventDefault()
                // 粘贴事件有一个 clipboardData 的属性,提供了对剪贴板的访问
                // clipboardData 的 getData(fomat) 从剪贴板获取指定格式的数据
                const text = (e.originalEvent || e).clipboardData.getData('text/plain') || prompt('在这里输入文本')
                // 插入
                document.execCommand('insertText', false, text)
            }
            $btns.forEach((el) => {
                el.onclick = function () {
                    // 获取值
                    const dataVal = el.getAttribute('data-val')
                    $input.focus()
                    // 获取选定对象
                    const selection = getSelection()
                    // 判断是否有最后光标对象存在
                    if (lastEditRange) {
                        // 存在最后光标对象,选定对象清除所有光标并添加最后光标还原之前的状态
                        selection.removeAllRanges()
                        selection.addRange(lastEditRange)
                    }
                    // console.log(selection.anchorNode.nodeName)
                    if (selection.anchorNode.nodeName === '#text') { // 在文本中插入
                        // 如果是文本节点则先获取光标对象
                        const range = selection.getRangeAt(0)
                        // 获取光标对象的范围界定对象,一般就是 textNode 对象
                        const textNode = range.startContainer
                        // 获取光标位置
                        const rangeStartOffset = range.startOffset
                        // 文本节点在光标位置处插入新的表情内容
                        textNode.insertData(rangeStartOffset, dataVal)
                        // 光标移动到到原来的位置加上新内容的长度
                        range.setStart(textNode, rangeStartOffset + dataVal.length)
                        // 光标开始和光标结束重叠
                        range.collapse(true)
                        // 清除选定对象的所有光标对象
                        selection.removeAllRanges()
                        // 插入新的光标对象
                        selection.addRange(range)
                    } else if (selection.anchorNode.nodeName === 'DIV') { // 内容为空时,点击按钮
                        const dataValText = document.createTextNode(dataVal)
                        $input.appendChild(dataValText)
                        // 创建新的光标对象
                        const range = document.createRange()
                        // 光标对象的范围界定为新建的表情节点
                        range.selectNodeContents(dataValText)
                        // 光标位置定位在表情节点的最大长度
                        range.setStart(dataValText, dataValText.length)
                        // 使光标开始和光标结束重叠
                        range.collapse(true)
                        // 清除选定对象的所有光标对象
                        selection.removeAllRanges()
                        // 插入新的光标对象
                        selection.addRange(range)
                    }
                    resetPlaceholder()
                }
            })

JS 实现复制功能(document.execCommand)

function copy(text) {
    const input = document.createElement('input');
    document.body.appendChild(input);
    input.setAttribute('value', text);
    input.select();
    if (document.execCommand('copy')) {
      document.execCommand('copy');
      console.log('复制成功');
    }
    document.body.removeChild(input);
}

参考引用:
1. contenteditable change events
2. html 元素 contenteditable 属性如何定位光标和设置光标
3. contenteditable 插入及粘贴纯文本内容