캔버스는 아무것도 공짜로 주지 않는다 - 텍스트 편집기를 직접 만들며 세 층을 갈아탄 기록
캔버스에서 contenteditable로, 다시 textarea로 갈아타며 텍스트 편집기를 직접 만든 실험 기록. 접근성이 캔버스를 기각한 이유와 spellcheck 하나를 못 찾아 며칠을 잃은 일화, UTF-16 코드 단위 함정까지 담았다.
캔버스는 아무것도 공짜로 주지 않는다
TL;DR
- 출발점이 냉소적인데 요즘은 서브라임 텍스트 같은 것을 안 만든다는 말이 많은 사람에게 공명했고 요즘 소프트웨어는 쓰레기인데 그것이 자기는 쓰레기를 잘 만든다는 생각으로 이어졌다는 것이다. 그래서 기준선이 낮게 잡힌다. VS 코드는 div 수프 지옥 풍경인 모나코 편집기 위에 지어졌고, 그것이 기준이라면 실수할 여지가 많다.
- 첫 시도인 캔버스가 기각되는 과정이 명확한데 최소 기능 다섯 개를 구현했지만 캔버스는 아무것도 공짜로 주지 않았고 텍스트 선택과 되돌리기 이력, 여러 줄 붙여넣기, 넘침 스크롤이 전부 없었다. 결정적 이유는 성능이 아니라 캔버스가 완전히 접근 불가능하고, 기능을 계속 더해도 근본적인 접근성 문제를 해결하는 것이 아니라는 점이었다.
- 두 번째 시도가 발상 전환인데 캔버스에 텍스트를 렌더링하는 대신 넘침 div에 그냥 네이티브로 렌더링하고 contenteditable 속성으로 편집 가능하게 만드는 것이다. 얻는 것이 크다. 네이티브 텍스트 선택과 되돌리기 이력이 생기고 아주 많은 접근성 이점이 브라우저에 의해 공짜로 배선되는데, 일정한 문자 수를 넘으면 이상한 성능 문제가 나타났다.
- 가장 아픈 일화가 한 속성에 있는데 spellcheck 같은 속성을 반드시 비활성화해야 입력 지연 급증을 피할 수 있고 그 수정을 발견하는 데 며칠이 걸렸다는 것이다. 그리고 세 번째 층으로 넘어간다. 평범한 textarea가 더 긴 텍스트에는 훨씬 성능이 좋은데, 다만 CSS 하이라이트를 쓸 수 없어 구문 강조를 위해 세 번째 층이 필요했다.
- 결론이 자기 평가로 정리되는데 텍스트 편집기의 90퍼센트처럼 보이는데 기능은 1퍼센트라는 것이고, 여기서부터 나머지 올빼미를 그리는 것은 꽤 간단하지만 탭 들여쓰기 같은 사소한 것들을 생각하면 멈춘다. 마지막에 남긴 함정이 인상적이다. 자바스크립트 문자열과 텍스트 범위가 UTF-16 코드 단위로 작동하므로 순진하게 버그를 만들기 쉽다.
Source
Fine, I’ll build my own text editor! — David Bushell, 2026년 9월 1일
Knowledge
기준선을 낮게 잡는 데서 시작한다
글은 앞선 반응에서 출발한다. 요즘은 서브라임 텍스트 같은 것을 안 만든다는 말이 많은 사람에게 공명했다는 것이다. 그리고 판정과 자기 인식이 붙는다. 요즘 소프트웨어는 쓰레기인데 그것이 생각을 하게 만들었고 자기는 쓰레기를 만드는 데 능하다는 것이다. 그래서 질문이 나온다. 왜 자기 텍스트 편집기를 만들 수 없겠느냐는 것이다.
기준선이 곧바로 설정된다. VS 코드가 모나코 편집기 위에 지어졌고 그것이 div 수프 지옥 풍경이라는 것이다. 개인 이력도 붙는다. 인텔이 든 자기 맥이 여러 해 동안 너무 느려서 VS 코드 열차에 늦게 탔고 애플 실리콘을 사면서 그 문제가 해결됐다는 것이다. 그래서 결론이 나온다. 그것이 기준이라면 실수할 여지가 많다는 것이다.
첫 번째 층 — 캔버스
첫 실험은 모든 것을 캔버스 요소에 렌더링하는 것이다. 그리고 겉으로는 안 보이는 대가가 지목된다. 그 그림을 초당 60에서 120프레임으로 렌더링하기 위해 CPU가 많은 일을 하고 있다는 것이다. 그리고 텍스트 편집기에 상호작용성 부족이 명백한 문제라고 덧붙인다.
최소 기능 다섯 개를 목록으로 만들어 구현했다고 한다. 포인터를 눌러 텍스트 커서를 위치시키는 것, 화살표 키로 커서를 옮기는 것, 현재 줄을 강조하는 것, 타이핑해서 텍스트를 넣는 것, 그리고 화려한 커서 애니메이션이다.
빔 바인딩에 대한 선제 대응이 재미있다. 빔 바인딩에 대해 언급하기 전에 조용히 하라며 자기에게는 더 급한 문제가 있다는 것이다.
그리고 이 절의 핵심 문장이 나온다. 캔버스가 아무것도 공짜로 주지 않는다는 것이다. 바람직한 기능 여럿 가운데 없는 것이 넷으로 열거된다. 텍스트 선택과 되돌리기 및 다시 하기 이력, 여러 줄 붙여넣기, 그리고 넘침 스크롤이다.
마지막 것이 결정적이라고 지목된다. 그리고 우회 방법이 솔직하게 밝혀진다. 커스텀 탄성 스크롤바를 구현하기에는 인생이 너무 짧으므로 속임수를 쓰기로 하고 숨겨진 요소에서 네이티브 브라우저 넘침을 쓰기로 했다는 것이다. 방식이 구체적이다. div의 크기를 캔버스 텍스트에 맞춰 조정하고 스크롤 위치를 써서 캔버스의 렌더링 오프셋을 계산한다는 것이다.
그런데 만족과 낙담이 동시에 온다. 진행 상황이 마음에 들면서도 낙담한 이유가 명확하다. 캔버스가 완전히 접근 불가능하다는 것이다. 그래서 판단이 내려진다. 텍스트 선택과 다른 기능을 계속 더할 수는 있지만 근본적인 접근성 문제를 해결하는 것이 아니라는 것이다. 그리고 더 나은 아이디어가 있었다고 이어진다.
두 번째 층 — contenteditable
발상 전환이 단순하다. 캔버스에 텍스트를 렌더링하는 대신 넘침 div에 그냥 네이티브로 렌더링하고 contenteditable 속성으로 편집 가능하게 만들 수 있다는 것이다. 그리고 그 속성에 코드에 완벽한 값이 있다고 지목한다. plaintext-only 값이고 모든 내용이 단일 텍스트 노드 안에 남는다는 것이다.
실제 코드가 제시된다. div에 contenteditable을 plaintext-only로 두고 autocapitalize와 autocorrect를 off로, spellcheck를 false로, translate를 no로 설정하는 형태다.
그리고 이 글에서 가장 아픈 일화가 붙는다. spellcheck 같은 속성을 반드시 비활성화해야 입력 지연 급증을 피할 수 있다는 것이고 그 수정을 발견하는 데 며칠이 걸렸는지 맞혀 보라며 며칠이라고 스스로 답한다.
얻는 것이 크다. contenteditable을 쓰면 네이티브 텍스트 선택과 되돌리기 이력 등이 생기고 아주 많은 접근성 이점이 브라우저에 의해 공짜로 배선된다는 것이다.
커스텀 커서를 유지하는 방법도 밝힌다. 선택 API가 지표를 제공하므로 그것으로 커스텀 텍스트 커서 렌더링을 계속한다는 것이다. 그리고 선택 영역에 대한 CSS 의사 요소도 쓸 수 있으므로 그것도 스타일링할 수 있다고 한다. 다만 자기 검열이 붙는다. 네이티브 캐럿 색을 보이지 않게 설정했는데 그것이 아마 하지 말아야 할 일이라는 것이다.
그런데 새 문제가 나타난다. contenteditable 기법이 유망하지만 일정한 문자 수를 넘으면 이상한 성능 문제를 알아차렸다는 것이다. 그리고 관찰이 구체적이다. 크로미엄 브라우저가 웹킷이나 지금 파이어폭스가 무엇이든 그것보다 성능이 나쁜데, 예측 불가능하다는 것이다.
세 번째 층 — textarea
다음 질문이 자연스럽다. 평문 contenteditable 대신 단순한 textarea가 실현 가능하겠느냐는 것이다. 답이 짧다. 그렇다는 것이고 알고 보니 textarea가 더 긴 텍스트에는 훨씬 성능이 좋다는 것이다.
마지막 데모에는 구문 강조까지 추가했다고 밝힌다. 그런데 계획이 바뀐 이유가 설명된다. 원래 계획은 contenteditable 요소에 커스텀 CSS 하이라이트를 쓰는 것이었는데, textarea는 CSS 하이라이트를 쓸 수 없으므로 세 번째 층이 필요했다는 것이다. 그래서 데모 목적으로 보이는 줄들에 대해 div 수프를 좀 추가해 마이크로라이터를 적용했다고 한다.
편집으로 추가된 정보도 있다. 새 OpaqueRange API가 textarea에 커스텀 하이라이트를 열어 준다고 들었다는 것이고 멋지다고 덧붙인다. 두 번째 편집에서는 EditContext API가 캔버스 입력을 개선한다고 언급한다.
성능 병목도 하나 더 지목된다. CSS 하이라이트가 너무 많은 것이 또 다른 성능 병목이라는 것이다. 그래서 더 견고한 해법이 제시된다. 트리시터로 구문 트리를 생성하고 그것을 걸어 보이는 줄에 대해서만 하이라이트를 생성하는 것이라는 것이다. 그리고 가상 스크롤링을 완전히 피하고 싶었지만 역방향 스티키 기법으로 개선할 수 있다고 덧붙인다. 아니면 편집할 파일 크기가 성능 벽에 닿지 않으므로 contenteditable로 돌아갈 수도 있다고 한다.
90퍼센트처럼 보이고 기능은 1퍼센트
자기 평가가 솔직하다. 텍스트 편집기의 90퍼센트처럼 보이는데 기능은 1퍼센트라는 것이다. 그리고 여기서부터 나머지 올빼미를 그리는 것은 꽤 간단하다고 표현한다.
그런데 멈추는 이유가 구체적이다. 계속 그리고 싶은 마음이 들지만 탭 들여쓰기 같은 사소한 것들을 생각한다는 것이다. 그리고 현재 상태를 밝힌다. 지금은 탭 키를 가로채 공백 두 개를 넣고 있다는 것이다.
그래도 성과가 평가된다. 데모들이 최적화되지 않았고 완벽하게 접근 가능한 것과는 거리가 멀지만 적어도 지는 위치에서 시작하고 있지는 않다는 것이다. 그리고 대비가 마지막에 온다. 캔버스에 렌더링하는 것은 악몽이었을 것이라는 것이다. 그래서 이 프로젝트를 비 오는 날을 위해 보관해 둔다고 정리한다.
마지막에 남긴 함정
닫는 부분에 기술적 함정 하나를 남긴다. 자바스크립트 문자열과 텍스트 범위가 UTF-16 코드 단위로 작동하므로 순진하게 버그를 만들기 쉽다는 것이다. 그리고 자기 데모들도 버그로 가득할 것이라고 인정한다.
그래서 코드 예시로 마무리한다. 초록 레몬 이모지 하나의 길이를 세 가지 방식으로 재는 것이다. 문자열 길이 속성으로는 5가 나오고 전개 연산자로 배열을 만들면 3이 나오며 Intl.Segmenter를 자소 단위로 만들어 분절하면 1이 나온다는 것이다. 그리고 이것을 너드 스나이프라고 부른다.
글 끝에는 저자의 AI 정책도 붙어 있다. 사람이 썼다는 것과, 모든 의견이 자기 것이며 대형 언어 모델의 것이 아니라는 것이다. 그리고 자기가 쓰는 모든 것이 백 퍼센트 사람이 쓴 것인데 신경 쓰기 때문이라고 밝힌다.
더 생각해보기
- 캔버스가 접근성 때문에 기각됐다면, 성능이 뛰어난 렌더링 방식과 접근성은 근본적으로 상충하는가.
- spellcheck 하나를 못 찾아 며칠을 잃은 일은, 웹 플랫폼의 성능 함정이 문서화되는 방식에 어떤 문제를 보여 주는가.
- contenteditable이 일정 문자 수를 넘으면 예측 불가능하게 느려진다면, 실무에서 그 임계를 어떻게 미리 알 수 있는가.
- textarea가 더 성능이 좋다는 발견은 왜 널리 알려져 있지 않은가. 편집기 개발자들이 왜 다른 선택을 하는가.
- 세 층을 갈아타며 결국 브라우저 네이티브 기능에 의존하게 된 경로는, 직접 만드는 것의 한계를 어디까지 보여 주는가.
- 90퍼센트처럼 보이고 기능은 1퍼센트라는 자기 평가는, 프로토타입의 완성도 착시를 어떻게 경계하게 하는가.
- UTF-16 코드 단위 함정은 텍스트를 다루는 다른 도구들에도 같은 방식으로 존재하는가.
- 트리시터로 보이는 줄만 하이라이트하는 접근은 어느 규모부터 필수가 되는가.
- 사람이 썼다는 정책을 명시하는 것이 기술 글쓰기에서 어떤 신호로 작동하는가.
