デベロッパーがウェブ アプリケーションに高度な編集機能を組み込むことは、必ずしも簡単ではありませんでした。ウェブ プラットフォームでは、<input>
や <textarea>
などの要素を使用するか、要素に contenteditable
属性を適用することで、書式なしテキストと HTML ドキュメントの両方を編集できます。ただし、これらの要素タイプの基本機能では、デベロッパーがアプリで実現したいことを実現できないことがよくあります。
デベロッパーは、ユーザーが必要とする機能を実装した独自のカスタム エディタ ビューを実装することがよくありました。エディタ ビューは複雑な DOM で作成することも、<canvas>
要素で作成することもできますが、デベロッパーがテキスト入力を受け取る唯一の方法は、フォーカスされた編集可能な要素を必要とするため、ページのどこかに非表示の contenteditable
要素を配置する必要があります。
ユーザーはアプリのカスタム エディタ ビューでコンテンツを直接編集しているように見えますが、実際には、デベロッパーが非表示の要素のイベント ハンドラで入力を受け取り、表示されるエディタ ビューにミラーリングしています。これにより、デベロッパーは、非表示の contenteditable
要素でブラウザのデフォルトの編集動作と競合することになり、問題が発生する可能性があります。
この種の問題に対処するため、Microsoft Edge チームは EditContext の標準化を推進しました。これは、ブラウザのデフォルトの編集動作に縛られることなく、デベロッパーがテキスト入力を直接受け取ることができる新しいウェブ プラットフォーム API です。
実際の例
たとえば、ユーザーが Word Online で共同編集している場合です。ユーザーは共同編集を行い、お互いの変更内容とカーソルの位置を確認できます。ただし、1 人の共同編集者が入力方法エディタ(IME)ウィンドウを使用して日本語のテキストを入力している場合、IME ユーザーが入力を完了するまで、他のユーザーによる変更が反映されるようにエディタが更新されることはありません。これは、IME コンポジションがアクティブなときに編集中の DOM 領域を変更すると、コンポジションが不用意にキャンセルされる可能性があるためです。IME ウィンドウが閉じられるまでアプリケーションが待機してビューを更新するため、遅延が発生し、コラボレーションが妨げられる可能性があります。
デベロッパーとユーザーの両方のエクスペリエンスを向上させるには、テキスト入力を 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 でバグを送信してください。