对开发者来说,在 Web 应用中集成高级编辑功能并非总是一件容易的事。Web 平台支持使用 <input>
和 <textarea>
等元素或通过向元素应用 contenteditable
属性,对纯文本和 HTML 文档进行修改。不过,这些元素类型的基本功能通常不足以满足开发者希望在应用中实现的目标。
开发者通常会最终实现自己的自定义编辑器视图,以实现用户需要的功能。编辑器视图可以使用复杂的 DOM(甚至 <canvas>
元素)构建,但由于开发者接收文本输入的唯一方式是聚焦于可编辑元素,因此他们仍然需要在网页上的某个位置放置一个隐藏的 contenteditable
元素。
这样一来,虽然用户似乎是在应用的自定义编辑器视图中直接修改内容,但开发者实际上是在隐藏元素中使用事件处理脚本接收输入,然后将其镜像到可见的编辑器视图中。这可能会导致问题,因为开发者最终会与隐藏的 contenteditable
元素中的浏览器默认编辑行为进行斗争。
为了解决这类问题,Microsoft Edge 团队推动了 EditContext 的标准化。EditContext 是一个新的 Web 平台 API,可让开发者直接接收文本输入,而无需绑定到浏览器的默认编辑行为。
真实示例
例如,当用户在 Word Online 中协作时。用户可以协同编辑,并查看对方的更改和光标位置。不过,如果某位协作者使用输入法编辑器 (IME) 窗口撰写日语文本,那么在 IME 用户完成撰写之前,其编辑器不会更新以显示其他用户所做的更改。这是因为,在有有效的 IME 组合时更改正在修改的 DOM 区域可能会导致组合过早取消。应用必须等到 IME 窗口关闭才能更新视图,这可能会导致延迟并妨碍协作。
为了提供更好的开发者和用户体验,开发者需要一种方法来将文本输入与 HTML DOM 视图分离。EditContext API 就是解决此问题的方案。
EditContext 基础知识
借助 EditContext,您可以直接通过 EditContext API Surface 接收文本和组合输入,而不是通过观察 DOM 的更改。这样,您就可以更严格地控制输入的处理方式,甚至可以为 <canvas>
元素添加可编辑性。
将 EditContext 实例与元素相关联可使其可修改:
// This will be our editable element.
const element = document.querySelector('#editor-element');
// Creating the EditContext object.
const editContext = new EditContext();
// Associating the EditContext object with our DOM element.
// The element is now focusable and can receive text input.
element.editContext = editContext;
// In order to render the text typed by the user onto the
// page, as well as the user's selection, you'll need to
// receive the input in a textupdate event callback.
editContext.addEventListener('textupdate', event => {
element.textContent = editContext.text;
// For brevity, the code to render the selection
// isn't shown here.
renderSelection(event.selectionStart, event.selectionEnd);
});
作者的责任
使用 EditContext API 可以更轻松地支持高级输入法,例如 IME 组合窗口、表情符号选择器和其他操作系统输入界面。为了在可编辑元素中实现所有这些功能,EditContext API 需要一些信息。除了渲染文本和选择内容之外,您在使用 EditContext API 时还必须执行一些其他操作。
管理可编辑区域的边界,或用户的选择发生变化时
每当可编辑区域的大小或用户的选择发生变化时,调用 updateControlBounds()
和 updateSelectionBounds()
方法以通知 EditContext 实例。这有助于平台确定在何处显示 IME 窗口和其他平台专用编辑界面。
// It's necessary to provide bounds information because EditContext
// is generic enough to work with any type of web editor, even
// <canvas>-based editors. The API doesn't make any assumptions as
// to how the editor is implemented or how the selection is rendered.
// Bounds are given in the client coordinate space.
const controlBound = editorElement.getBoundingClientRect();
const selection = document.getSelection();
const selectionBound = selection.getRangeAt(0).getBoundingClientRect();
editContext.updateControlBounds(controlBound);
editContext.updateSelectionBounds(selectionBound);
管理编辑器界面的位置
监听 characterboundsupdate
事件并调用 updateCharacterBounds()
进行响应,以帮助平台确定在何处显示 IME 窗口和其他特定于平台的编辑界面。
应用格式
监听 textformatupdate
事件,并将事件指定的格式应用于编辑器视图。IME 在撰写某些语言时会使用这些文本装饰。例如,日语输入法会使用下划线来显示文本的哪个部分正在被主动撰写。
处理富文本编辑行为
监听 beforeinput
事件,以处理您想要支持的任何富文本编辑行为,例如用于将文本加粗或斜体的热键,或应用拼写检查更正。
管理用户选择的更改
当用户的选择因键盘或鼠标输入而发生变化时,您需要通知 EditContext 实例发生的更改。这是必要的,因为 EditContext API 适用于许多用例,包括使用 <canvas>
元素呈现的编辑器,在这种情况下,浏览器无法自动检测选择更改。
document.addEventListener('selectionchange', () => {
const selection = document.getSelection();
// EditContext doesn't handle caret navigation, so all the caret navigation/selection that happens
// in DOM space needs to be mapped to plain text space by the author and passed to EditContext.
// This example code assumes the editable area only contains text under a single node.
editContext.updateSelection(selection.anchorOffset, selection.focusOffset);
});
如果您要与 EditContext 搭配使用的元素是 <canvas>
元素,则还需要实现选择和光标导航行为,例如使用箭头键浏览文本。此外,浏览器的内置拼写检查功能仅适用于非 <canvas>
元素。
EditContext 与 contenteditable
如果您要实现功能齐全的编辑器,并希望完全控制文本输入的处理方式,或者要添加多用户协同编辑等高级功能,EditContext 是一个不错的选择。不过,鉴于使用 EditContext 的上述所有要求,如果您只需要简单的文本编辑支持,可能仍需要使用 <input>
、<textarea>
元素或 contenteditable
属性。
展望未来
Microsoft Edge 团队与 Chrome 工程师合作,在 Chromium 中实现了 EditContext,并将在 Chrome 和 Edge 的 121 版(2024 年 1 月)中提供该功能。目前,该 API 仅适用于基于 Chromium 的浏览器,但您可以了解 Mozilla 和 WebKit 对 EditContext API 的立场。
我们希望让 Web 开发者更轻松地在 Web 上构建强大的自定义编辑体验,我们认为 EditContext API 通过解决现有问题并提供更直接的方式来处理文本输入,从而实现了这一目标。
如需详细了解该 API,请参阅 MDN 文档。如需提交有关该 API 设计的反馈,请在 EditContext API 的 GitHub 代码库中打开一个问题。如需报告 API 实现中的 bug,请访问 crbug.com 提交 bug。