PAYATHON 2026

beforeinput 中按单词删除的范围如何确定?

数据阿明

对 <input> 和 <textarea>,beforeinput 的标准接口无法直接给出浏览器即将按单词删除的范围。selectionStart 和 selectionEnd 表示当前选区;对于 contenteditable,可以通过 event.getTargetRanges() 读取目标范围。规范也没有要求所有浏览器使用同一个正则表达式来确定单词及相邻空白的删除边界。

为什么选区没有包含即将删除的单词?

问题中的情况是选区折叠成光标,即 selectionStart === selectionEnd。按单词删除是一项编辑意图,浏览器不必先将要删除的内容变成可见选区,因此当前选区不能代表删除范围。

Input Events Level 2 将 deleteWordBackward 和 deleteWordForward 分别定义为删除光标前、后的一个单词,但没有规定统一的分词正则或完整的空白吞并算法。该文档目前是工作草案,不能据此假定各浏览器实际采用的边界完全一致。Input Events Level 2

单个空白与连续多个空白的处理不同,并不足以推导出通用规则。具体边界需要结合浏览器、操作系统和文本内容验证;快捷键与编辑意图的映射也遵循平台约定。编辑意图的定义

contenteditable:读取 getTargetRanges()

对于浏览器产生的上述两类 beforeinput 事件,规范为 contenteditable 提供了目标范围。对于 <input> 和 <textarea>,getTargetRanges() 返回空数组,因此只检查方法是否存在还不够。getTargetRanges() 的规定

<div id="editor" contenteditable="true">one  two three</div>
<script>
const editor = document.querySelector('#editor');
const wordDeletes = new Set([
  'deleteWordForward',
  'deleteWordBackward'
]);

editor.addEventListener('beforeinput', (event) => {
  if (!wordDeletes.has(event.inputType)) return;
  if (typeof event.getTargetRanges !== 'function') return;

  for (const range of event.getTargetRanges()) {
    console.log({
      startContainer: range.startContainer,
      startOffset: range.startOffset,
      endContainer: range.endContainer,
      endOffset: range.endOffset
    });
  }
});
</script>

返回的范围是 StaticRange,其边界由 DOM 节点和节点内偏移组成,可能跨越多个文本节点,不能直接用作整个编辑器文本的字符串索引。若要将它映射到自己的文档模型,应在处理本次 beforeinput 时读取这些边界。

input、textarea:允许删除后,再确定实际范围

如果要记录浏览器实际删除了什么,可以在 beforeinput 中保存旧值和光标位置,再在 input 中读取新值。

下面的示例适用于 <input type="text">,前提是选区原本折叠,本次变化仅为沿指定方向删除一段连续文本,且没有其他脚本同时修改值。示例根据删除长度构造候选范围,再检查该范围能否解释新旧值的变化。

<input id="text" type="text" value="one  two three">
<script>
const field = document.querySelector('#text');
const wordDeletes = new Set([
  'deleteWordForward',
  'deleteWordBackward'
]);
let pending = null;

field.addEventListener('beforeinput', (event) => {
  pending = null;
  if (!wordDeletes.has(event.inputType)) return;
  if (field.selectionStart !== field.selectionEnd) return;

  pending = {
    value: field.value,
    caret: field.selectionStart,
    type: event.inputType
  };
});

field.addEventListener('input', (event) => {
  const before = pending;
  pending = null;
  if (!before || event.inputType !== before.type) return;

  const after = field.value;
  const count = before.value.length - after.length;
  if (count <= 0) return;

  const start = before.type === 'deleteWordBackward'
    ? before.caret - count
    : before.caret;
  const end = start + count;

  if (start < 0 || end > before.value.length) return;
  if (before.value.slice(0, start) + before.value.slice(end) !== after) {
    return;
  }

  console.log({
    start,
    end,
    deleted: before.value.slice(start, end)
  });
});
</script>

得到的范围是旧值中的半开区间 [start, end),包括实际删除的空白。索引以 UTF-16 code unit 为单位,与 JavaScript 字符串操作一致,不能直接视为用户可见字符的数量。

这个方案只能在操作完成后还原范围,无法提前决定是否允许原生删除。如果事件被取消且没有发生删除,也就无法得到相应的删除结果。其他监听器改写文本或发生复杂编辑转换时,需要结合应用的数据模型处理,不能仅凭长度差认定删除范围。

必须提前控制范围时怎么办?

若必须在 <input> 或 <textarea> 的删除发生前决定边界,可以自行定义删除规则,在可取消的 beforeinput 中调用 preventDefault(),再按自己的规则更新文本和光标。

需要明确规定单词、标点、连续空白以及光标位于词中时的处理方式。简单正则可以实现特定业务规则,但不能保证复现浏览器的原生行为;自行接管编辑还需要处理撤销、重做和应用状态同步。

备注:内容仅供参考。