EditContext API를 사용하여 맞춤 웹 편집 환경을 빌드하는 새로운 방법 소개

개발자가 웹 애플리케이션에 고급 편집 기능을 통합하는 것은 항상 간단한 작업은 아닙니다. 웹 플랫폼에서는 <input><textarea>와 같은 요소를 사용하거나 요소에 contenteditable 속성을 적용하여 일반 텍스트와 HTML 문서에 모두 편집 기능을 제공합니다. 그러나 이러한 요소 유형의 기본 기능으로는 개발자가 앱에서 달성하고자 하는 목표를 달성하기에 충분하지 않은 경우가 많습니다.

개발자가 사용자에게 필요한 기능을 구현하는 맞춤 편집기 뷰를 구현하는 경우가 많습니다. 편집기 뷰는 복잡한 DOM 또는 <canvas> 요소로 빌드할 수 있습니다. 하지만 개발자가 텍스트 입력을 받는 유일한 방법은 포커스가 설정된 수정 가능한 요소가 필요하므로 숨겨진 contenteditable 요소를 페이지 어딘가에 배치해야 합니다.

결과적으로 사용자가 앱의 맞춤 편집기 뷰에서 콘텐츠를 직접 수정하는 것처럼 보이지만, 개발자는 실제로 숨겨진 요소의 이벤트 핸들러로 입력을 수신한 다음 표시되는 편집기 뷰에 미러링합니다. 이로 인해 개발자가 숨겨진 contenteditable 요소에서 브라우저의 기본 수정 동작과 싸우기 때문에 문제가 발생할 수 있습니다.

이러한 문제를 해결하기 위해 Microsoft Edge팀은 브라우저의 기본 수정 동작에 구속되지 않고 개발자가 직접 텍스트 입력을 수신할 수 있는 새로운 웹 플랫폼 API인 EditContext의 표준화를 추진했습니다.

실제 예시

예를 들어 사용자가 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 이벤트를 수신 대기하고 이에 대한 응답으로 updateCharacterBounds()를 호출하여 플랫폼에서 IME 창 및 기타 플랫폼별 수정 UI를 표시할 위치를 결정할 수 있도록 합니다.

서식 적용 중

textformatupdate 이벤트를 수신 대기하고 이벤트에 지정된 형식을 편집기 뷰에 적용합니다. 이러한 텍스트 장식은 특정 언어를 작성할 때 IME에서 사용합니다. 예를 들어 일본어 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에서 버그를 제출하세요.