隆重推出使用 EditContext API 打造自定义网页编辑体验的新方法

对开发者来说,在 Web 应用中集成高级编辑功能并非总是一件容易的事。Web 平台支持使用 <input><textarea> 等元素或通过向元素应用 contenteditable 属性,对纯文本和 HTML 文档进行修改。不过,这些元素类型的基本功能通常不足以满足开发者希望在应用中实现的目标。

开发者通常会最终实现自己的自定义编辑器视图,以实现用户需要的功能。编辑器视图可能使用复杂的 DOM(甚至使用 <canvas> 元素)构建,但由于开发者接收文本输入的唯一方式需要获得焦点的可编辑元素,因此他们仍然需要在其页面上的某个位置放置一个隐藏的 contenteditable 元素。

这样一来,虽然用户似乎是在应用的自定义编辑器视图中直接修改内容,但开发者实际上是在隐藏元素中使用事件处理脚本接收输入,然后将其镜像到可见的编辑器视图中。这可能会导致问题,因为开发者最终要与隐藏的 contenteditable 元素中的浏览器默认修改行为作斗争。

为了解决这类问题,Microsoft Edge 团队已推动 EditContext 的标准化。EditContext 是一种新的网络平台 API,可让开发者直接接收文本输入,而无需受制于浏览器的默认编辑行为。

一个真实示例

例如,当用户在 Word Online 中协作时。用户可以协同编辑并查看彼此的更改和光标位置。但是,如果某位协作者使用输入法 (IME) 窗口来撰写日语文本,那么在该 IME 用户完成其组合之前,其编辑器不会更新以显示其他用户所做的更改。这是因为,在有有效的 IME 组合时更改正在修改的 DOM 区域可能会导致组合过早取消。应用必须等到 IME 窗口关闭后才能更新视图,这可能会导致延迟和妨碍协作。

在 Word Online 中撰写文本时无法协作

为了提供更好的开发者和用户体验,开发者需要一种方法来将文本输入与 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 在编写某些语言时使用。例如,日语 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 月)一起发布。目前,它只能在基于 Chromium 的浏览器中使用,但您可以在 EditContext API 上查看 MozillaWebKit 的位置

我们希望让 Web 开发者更轻松地在 Web 上构建强大的自定义编辑体验,我们认为 EditContext API 通过解决现有问题并提供更直接的方式来处理文本输入,从而实现了这一目标。

如需详细了解该 API,请参阅 MDN 文档。如需提交有关该 API 设计的反馈,请在 EditContext API 的 GitHub 代码库中打开一个问题。要报告与 API 实现相关的错误,请在 crbug.com 上提交错误。