contenteditable 中 beforeinput 事件无法取消之谜
你是否也曾被 contenteditable 中的 beforeinput 事件搞得一头雾水?明明文档说可以取消,实际操作却毫无作用?最近在开发一个富文本编辑器时,我也遇到了这个棘手的问题。
事情是这样的,WebKit 官方博客信誓旦旦地表示,我们可以用 beforeinput 事件来控制 contenteditable 元素中的用户输入,甚至信誓旦旦地说可以取消。可是,当我满怀期待地在 Chrome 浏览器中测试他们的示例代码时,现实却给了我沉重的一击——压根不起作用!
经过一番抽丝剥茧的调查,我终于找到了问题的根源:虽然 WebKit 博客言之凿凿,但 Chrome 浏览器压根还没完全实现这个功能!也就是说,在当前版本的 Chrome 中,beforeinput 事件的 cancelable 属性就是个摆设,无法阻止用户的输入。
既然 beforeinput 事件这条路走不通,我们就得另辟蹊径了。好在天无绝人之路,我们可以用其他方法来实现对 contenteditable 元素的输入控制。
方案一:亡羊补牢,为时未晚——input 事件监听与回滚
既然无法阻止输入,那我们就等输入完成后再进行判断,如果发现不符合预期,直接回滚到之前的状态即可。
具体做法是监听 input 事件,在事件触发后检查输入内容是否符合要求。如果符合,那就万事大吉;如果不符合,就通过操作 DOM 将内容还原到之前的状态。
这种方法简单直接,但需要手动处理 DOM 更新,可能会影响性能,特别是需要频繁进行输入校验时。
const editor = document.getElementById('editor');
editor.addEventListener('input', (event) => {
// 检查输入内容是否符合预期
if (isInputValid(event.target.innerHTML)) {
// 输入有效,不做任何处理
return;
}
// 输入无效,回滚到之前的状态
event.target.innerHTML = getPreviousState();
});
// 模拟存储上一次有效状态
function getPreviousState() {
// 此处需要根据实际情况实现,比如可以使用 history.state 或者自定义变量来存储上一次的有效状态
return '上一次有效状态';
}
// 检查输入内容是否符合预期
function isInputValid(input) {
// 此处需要根据实际情况实现,比如可以使用正则表达式或者其他校验规则来判断输入是否有效
return input === '有效输入';
}
方案二:防患于未然——键盘事件监听与阻止默认行为
俗话说,最好的防守就是进攻。与其事后补救,不如从源头上掐断“祸根”。
对于某些特定按键触发的输入,我们可以监听键盘事件(比如 keydown),然后使用 event.preventDefault() 阻止默认行为。
这种方法的优点是精细度高,可以针对特定按键进行控制,缺点是需要处理各种按键组合,逻辑可能较为复杂,尤其是需要兼容各种浏览器和输入法时。
const editor = document.getElementById('editor');
editor.addEventListener('keydown', (event) => {
// 检查是否为需要阻止的按键
if (shouldPreventKey(event.key)) {
event.preventDefault();
}
});
// 检查是否为需要阻止的按键
function shouldPreventKey(key) {
// 此处需要根据实际情况实现,比如可以根据按键的 keyCode 或者 key 值来判断
return key === 'Enter'; // 阻止用户输入回车
}
方案三:借力打力——使用第三方库
正所谓“术业有专攻”,有些事情自己做比较麻烦,但借助专业的工具却能事半功倍。
市面上有很多专门用于处理 contenteditable 元素的第三方库,比如 ProseMirror、Slate 等,它们提供了更完善的输入控制机制,可以让我们更方便地实现所需功能。
这些库通常封装了底层的 DOM 操作和事件处理逻辑,并提供了更高级的 API,可以大大简化开发过程。
以下是一个使用 ProseMirror 库实现输入控制的示例:
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Schema, DOMParser } from 'prosemirror-model';
// 定义文档结构
const schema = new Schema({
nodes: {
doc: {
content: 'paragraph+'
},
paragraph: {
content: 'text*',
toDOM() { return ['p', 0] }
},
text: {
group: 'inline'
}
}
});
// 创建编辑器状态
const state = EditorState.create({
schema: schema,
doc: DOMParser.fromSchema(schema).parse(document.getElementById('editor'))
});
// 创建编辑器视图
const view = new EditorView(document.getElementById('editor'), {
state: state,
// 定义输入规则
dispatchTransaction(tr) {
// 在这里可以对输入进行校验和过滤
this.updateState(this.state.apply(tr));
}
});
虽然 beforeinput 事件在 contenteditable 元素中的取消功能暂时还没有完全实现,但我们仍然可以通过其他方法来实现对用户输入的控制。选择哪种方案取决于具体的业务需求和项目复杂度。
如果只是简单的输入校验,可以使用 input 事件监听和回滚的方式;如果需要更精细的控制,可以使用键盘事件监听和阻止默认行为;如果需要更强大的功能和更方便的 API,建议使用第三方库。