contenteditable
是一个可以使DIV可编辑的属性,可以用它来制作富文本编辑器。也许你会需要一个像 textarea 一样的 maxlength
属性,如果你没有做过,可能会觉得很简单,超过一定字数截掉不就行了。然而,并没有那么简单,下面是我在做这个的过程中遇到的问题:
- 超过字数替换
event.target.innerHTML
内容,出现光标回到开头,应该可以通过一点手段使它回到最后,但是如果是中间就麻烦了,所以放弃了这个方案。 - 使用
document.execCommand("insertHTML")
替换内容,可以使光标保持在最后,但是光标原来在中间也麻烦,还有一个问题,替换内容会使滚动条回到顶部,虽然可以通过代码滚回原来的位置,但是感觉体验还是不好。 - 怎么计算要截去哪些字符是个问题。
innerHTML
是带标签的,要截去内部文字比较麻烦,而且会存在一些转义字符类似<
、>
等。 - 字符达到限制,截去的都是尾部的内容,中间继续输入内容会保留。按照 textarea 的实现,当字符超过,不能继续输入是不是应该更合理?
既然替换内容这么多问题,那是不是可以通过禁止键盘输入,这个就要考虑KeyboardEvent
事件的。首先使用keydown
事件,判断innerText
超过字数,阻止默认事件,这个太粗暴了,把所有按键都阻止掉了,删除也不行。难度要筛选对应keyCode去阻止?这个要包含全部太繁琐,也容易出bug。
keypress
keypress
事件在键盘输入产生字符时才会触发,用这个去阻止默认事件。
const onKeypress = (event) => {
if (event.target.innerText.length >= props.maxlength) {
event.preventDefault();
}
};
compositionend
keypress
可以禁掉英文输入,但是中文拼音输入的时候不能阻止。这个时候要考虑使用compositionend
事件,MDN描述如下:
当文本段落的组成完成或取消时,compositionend 事件将被触发 (具有特殊字符的触发,需要一系列键和其他输入,如语音识别或移动中的字词建议)。
不过这个事件并不能阻止默认事件(只在谷歌试过),所以还是要当输入内容时,使用截去字符的方法,可是上面说到的替换文本问题,所以要考虑能不能取部分内容替换。execCommand
有个delete
命令,可以删除选中内容,要解决的是如何选中内容。经过测试,创建Range对象可以选中文案,而且选区创建在当前元素内或者段落。如图:
代码如下:
const range = document.createRange();
const sel = window.getSelection();
const node = sel.anchorNode;
range.selectNodeContents(node);
sel.removeAllRanges();
sel.addRange(range);
然后删除内容,重新添加新的内容,但是直接这样修改也有问题,新添加的内容在哪呢?每次修改后光标都在这行最后,好像不满足要求。解决这个问题,最简单的问题是把选区移动到内容添加处,然后把这部分内容删除掉后面多余的。
上面增加两行代码:
const range = document.createRange();
const sel = window.getSelection();
const offset = sel.anchorOffset; // 光标偏移位置
const node = sel.anchorNode;
range.selectNodeContents(node);
sel.removeAllRanges();
sel.addRange(range);
sel.extend(node, offset); // 移动选区到原来光标位置
然后就是替换内容了。事件内完整代码:
const onCompositionend = (event) => {
const diff = event.target.innerText.length - props.maxlength;
if (diff > 0) {
const range = document.createRange();
const sel = window.getSelection();
const offset = sel.anchorOffset;
const node = sel.anchorNode;
const text = node.textContent;
range.selectNodeContents(node);
sel.removeAllRanges();
sel.addRange(range);
sel.extend(node, offset);
document.execCommand('delete', false);
document.execCommand(
'insertText',
false,
text.substring(0, offset - diff)
);
}
};
其中火狐removeAllRanges
与addRange
可能会冲突,猜测removeAllRanges
是异步执行的,导致addRange
之后执行删除,可添加setTimeout
处理。
这个方案完美解决了替换内容后光标问题、滚动条问题、字符统计问题。不过execCommand
这个语法被弃用了,目前还没有较好的替代方案。
完整代码查看:[点这里]