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

高度な編集機能をウェブ アプリケーションに組み込むことは、デベロッパーにとって必ずしも簡単な作業ではありません。ウェブ プラットフォームでは、<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 を使用すると、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 イベントをリッスンし、IME ウィンドウやその他のプラットフォーム固有の編集 UI を表示する場所をプラットフォームが決定できるように、それに応じて updateCharacterBounds() を呼び出します。

書式を適用しています

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 の位置を確認できます。

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

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