저희는 노션과 같은 직관적이고 자연스러운 온라인 블록 기반 텍스트 에디터를 개발중입니다.

HTML의 contentEditable 속성을 활용해 구현하기로 결정했지만, 예상치 못한 여러 가지 문제점이 발생했습니다.

그래서 이 글을 통해 위 상황에서 겪은 문제를 해결해 나가는 과정을 공유하고자 합니다.

❓ 텍스트 입력 방법으로 contentEditable를 선택한 이유

처음에는 textarea를 사용하여 블록 단위의 입력을 구현하려 했으나, 블록 내 개별 항목들을 유연하게 다루기 어려운 문제가 있었습니다. 이를 해결하기 위해 contentEditable을 활용하는 방법을 알게 되었고, 보다 유연한 구성과 블록 기반의 에디터 구조를 구현할 수 있어 contentEditable로 변경하게 되었습니다.

하지만, 이 과정에서 여러 가지 문제점이 발생했습니다.

❗️ contentEditable에서 발생한 주요 문제점들

1. 입력 시 커서가 맨 앞으로 이동하는 문제

contentEditable을 사용할 때, 입력 시 커서가 자동으로 맨 앞으로 이동하는 현상이 발생했습니다.

입력을 할때마다 상태가 바뀌어 리렌더링이 발생하고, 이때 다시 포커스를 하게 되면서 커서의 위치가 해당 블록의 맨 앞으로 다시 위치하게 된다고 생각했습니다.

커서 앞으로 문제.gif

해결 시도

이를 해결하기 위해 입력 시마다 매번 커서의 위치를 저장한 후, 렌더링 이후 강제로 커서를 원래 위치로 복구하는 방식으로 해결하고자 하였습니다.

const handleInput = () => {
    const selection = window.getSelection();
    const range = selection?.getRangeAt(0);
    const cursorPosition = range?.startOffset || 0;

    setTimeout(() => {
        const newRange = document.createRange();
        const newSelection = window.getSelection();

        newRange.setStart(restoredBlock.childNodes[0] || restoredBlock, cursorPosition);
        newRange.collapse(true);

        newSelection?.removeAllRanges();
        newSelection?.addRange(newRange);
      }
    }, 0);
  };
  1. 현재 커서 위치를 range.startOffset을 통해 저장
  2. 리렌더링 이후 setTimeout()을 사용하여 커서를 원래 위치로 복구