隆重推出使用 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 月)一起发布。目前,该 API 仅适用于基于 Chromium 的浏览器,但您可以阅读 MozillaWebKit 在 EditContext API 中的位置

我们希望 Web 开发者能够更轻松地在网络上构建强大的自定义编辑体验,我们相信 EditContext API 通过解决现有挑战并提供更直接的文本输入处理方式来实现这一目标。

如需详细了解该 API,请参阅 MDN 文档。如需提交有关该 API 设计的反馈,请在 EditContext API 的 GitHub 代码库中提交相应问题。如需报告 API 实现方面的 bug,请前往 crbug.com 提交 bug。