EditContext API を使用してカスタムのウェブ編集エクスペリエンスを構築する新しい方法のご紹介

ウェブ アプリケーションに高度な編集機能を組み込むことは、デベロッパーにとって必ずしも簡単なことではありません。ウェブ プラットフォームでは、<input><textarea> などの要素を使用するか、contenteditable 属性を要素に適用することで、書式なしテキストと HTML ドキュメントの両方の編集可能性を提供します。ただし、多くの場合、これらの要素タイプの基本的な機能は、デベロッパーがアプリで実現したいことには不十分です。

デベロッパーは、ユーザーが必要とする機能を実装した独自のカスタム エディタ ビューを実装することがよくありました。エディタ ビューは複雑な DOM で作成することも、<canvas> 要素で作成することもできますが、デベロッパーがテキスト入力を受け取る唯一の方法は、フォーカスされた編集可能な要素を必要とするため、ページのどこかに非表示の contenteditable 要素を配置する必要があります。

ユーザーはアプリのカスタム エディタ ビューでコンテンツを直接編集しているように見えますが、実際には、デベロッパーが非表示の要素のイベント ハンドラで入力を受け取り、表示されるエディタ ビューにミラーリングしています。これにより、デベロッパーは、非表示の contenteditable 要素でブラウザのデフォルトの編集動作と競合することになり、問題が発生する可能性があります。

この種の問題に対処するため、Microsoft Edge チームは EditContext の標準化を推進しました。これは、ブラウザのデフォルトの編集動作に縛られることなく、デベロッパーがテキスト入力を直接受け取ることができる新しいウェブ プラットフォーム API です。

実際の例

たとえば、ユーザーが Word Online で共同編集している場合です。ユーザーは共同編集を行い、お互いの変更内容とカーソルの位置を確認できます。ただし、1 人の共同編集者が入力メソッド エディタ(IME)ウィンドウを使用して日本語のテキストを入力している場合、IME ユーザーが入力を完了するまで、そのエディタは更新されず、他のユーザーによる変更は表示されません。これは、IME コンポジションがアクティブなときに編集中の DOM 領域を変更すると、コンポジションが不用意にキャンセルされる可能性があるためです。アプリケーションは、IME ウィンドウが閉じられるまで待機しないとビューを更新できません。そのため、遅延が生じたり、コラボレーションが妨げられたりする可能性があります。

テキストの作成中に Word Online でコラボレーションできない

デベロッパーとユーザーの両方のエクスペリエンスを向上させるには、テキスト入力を HTML DOM ビューから分離する方法が必要です。EditContext API は、この問題の解決策です。

EditContext の基本

EditContext を使用すると、DOM の変更を監視することなく、EditContext API サーフェスから直接テキストと構成の入力を受け取ることができます。これにより、入力の処理方法をより細かく制御できるほか、<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 ウィンドウやその他のプラットフォーム固有の編集 UI を表示する場所をプラットフォームが決定できます。

// 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);

エディタ UI の位置を管理する

characterboundsupdate イベントをリッスンし、それに応じて updateCharacterBounds() を呼び出して、IME ウィンドウやその他のプラットフォーム固有の編集 UI を表示する場所をプラットフォームが決定できるようにします。

書式を適用しています

textformatupdate イベントをリッスンし、イベントで指定されたフォーマットをエディタビューに適用します。これらのテキスト デコレーションは、IME が特定の言語を作成する際に使用します。たとえば、日本語の IME では、入力中のテキストの部分が下線で示されます。

日本語文字の入力に使用される入力メソッド エディタ ウィンドウのスクリーンショット。

リッチテキスト編集の動作を処理する

beforeinput イベントをリッスンして、テキストの太字や斜体化のホットキー、スペルチェックの修正の適用など、サポートするリッチテキスト編集動作を処理します。

ユーザー選択の変更を管理する

キーボードまたはマウスの入力によってユーザーの選択が変更された場合は、その変更を EditContext インスタンスに通知する必要があります。これは、ブラウザが選択内容の変更を自動的に検出できない <canvas> 要素でレンダリングされるエディタなど、EditContext API が幅広いユースケースに適用されるため必要です。

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 に関する Mozilla の立場WebKit の立場をご覧ください。

Google は、ウェブ デベロッパーがウェブ上で優れたカスタム編集エクスペリエンスを簡単に構築できるようにしたいと考えています。EditContext API は、既存の課題に対処し、テキスト入力をより直接的に処理する方法を提供することで、これを実現できると考えています。

API の詳細については、MDN のドキュメントをご覧ください。API の設計に関するフィードバックを送信するには、EditContext API の GitHub リポジトリで問題を報告してください。API の実装に関するバグを報告するには、crbug.com でバグを送信してください。