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에서 버그를 제출하세요.