로그인, 목록, 폼까지는 그럭저럭 만들었는데 어느 날 글쓰기 편집기를 붙이라는 일이 떨어져요. 굵게 버튼을 누르면 글자가 굵어지고, 사진이 글 중간에 턱 하고 박히고, 목록도 리스트 모양으로 들어가는 그 편집기요. 저도 이걸 처음 맡았을 때 별거 아니겠지 했다가 2주를 통째로 날렸어요. 리치 에디터(rich text editor, 서식 편집기)는 웹에서 가장 손이 많이 가는 부품 중 하나거든요. 오늘은 이게 대체 뭐고, 왜 그렇게 어렵다고들 하는지 큰 그림부터 잡아볼게요.
15.1 리치 에디터가 대체 뭔가요?
리치 에디터는 사용자가 글자를 치면서 동시에 서식까지 입히는 편집기예요. 여기서 리치(rich)는 서식이 풍부하다는 뜻이에요. 굵게, 기울임, 제목, 목록, 링크, 사진, 표까지 글 안에 섞여 들어가죠. 화면에서 보이는 모습이 결과물과 똑같이 나온다고 해서 위지윅(WYSIWYG, 보이는 대로 얻는다)이라고도 불러요.
우리가 매일 쓰는 것들이 다 리치 에디터예요. 블로그 글쓰기 창, 메일 작성 화면, 노션 같은 문서 도구, 게시판 글쓰기 창까지요. 공통점은 글과 서식이 한 덩어리로 다뤄진다는 점이에요. 사용자는 워드 쓰듯 편하게 쓰지만, 그 편함 뒤에서 개발자는 꽤 복잡한 일을 처리하고 있어요.
15.2 그냥 textarea로는 안 되나요?
여기서 많은 분이 textarea(여러 줄 입력칸)면 충분하지 않냐고 물어요. 저도 처음엔 그랬고요. 그런데 textarea는 순수한 글자만 담아요. 아무리 쳐도 굵은 글씨도, 사진도, 색깔도 못 넣어요. 안에 든 건 그냥 줄바꿈 있는 텍스트 한 덩어리예요.
<textarea>여기엔 굵게도 사진도 못 넣어요</textarea>
textarea에 굵게 버튼을 달아봐야 눌러도 아무 일이 안 일어나요. 글자를 굵게 만들 표현 수단 자체가 없으니까요. 그래서 서식이 필요한 순간 textarea는 탈락이에요. 물론 사용자가 별표나 특수기호로 서식을 표시하게 하는 방법(마크다운, markdown)도 있지만, 그건 사용자가 규칙을 외워야 하죠. 워드처럼 버튼으로 척척 꾸미게 하려면 다른 그릇이 필요해요.
15.3 그럼 브라우저는 편집을 어떻게 지원해요?
브라우저에는 아주 특별한 속성이 하나 있어요. contenteditable(콘텐츠 편집 가능)이에요. 아무 요소에나 이걸 붙이면, 그 일반 영역이 편집창으로 변해요. div든 p든 상관없이요.
<div contenteditable="true">여기를 클릭해서 바로 쳐보세요</div>
이 div를 클릭하면 커서가 깜빡이고, 글자를 치면 그대로 들어가요. 게다가 이 안에서는 진짜 HTML이 살아 있어요. 글자를 굵게 만들면 안에 b 태그가 생기고, 줄을 바꾸면 새 문단 태그가 만들어져요. 즉 사용자가 보는 서식이 곧 DOM(문서 구성 조각) 구조로 저장되는 거예요. 리치 에디터의 가장 밑바닥 재료가 바로 이 속성이에요. 뒤에 나올 유명한 에디터 라이브러리들도 결국 이 contenteditable 위에 얹혀 있어요.
재밌는 건 이 편집창에 공짜로 딸려오는 기능이 꽤 많다는 거예요. 커서 옮기기, 글자 지우기, 드래그로 선택하기, 심지어 복사와 붙여넣기까지 브라우저가 알아서 해줘요. 그래서 처음 만져보면 이걸로 다 되겠다 싶어요. 하지만 이 편리함이 곧 함정의 입구이기도 한데, 그 이야기는 뒤에서 제대로 풀 거예요.
15.4 execCommand로 서식을 넣는다던데요?
예전엔 document.execCommand라는 명령으로 서식을 넣었어요. 선택한 글자에 굵게를 먹이고 싶으면 이렇게요.
document.execCommand("bold");
document.execCommand("insertUnorderedList");
한 줄이면 선택 영역이 굵어지고, 목록도 만들어졌어요. 편해 보이죠? 그런데 이 명령은 지금 공식적으로 폐기(deprecated) 상태예요. 표준에서 밀려났고, 브라우저마다 결과 마크업이 제각각이라 믿고 쓰기 어렵거든요. 같은 굵게라도 어떤 브라우저는 b 태그를, 어떤 브라우저는 style이 붙은 span을 만들어요. 그래서 요즘 만드는 에디터는 execCommand에 기대지 않아요. 이 폐기의 여파가 왜 큰지는 다음 이야기에서 자세히 다뤄요.
15.5 요즘 에디터는 그럼 어떻게 동작해요?
요즘 방식의 핵심은 브라우저에 다 맡기지 않는다예요. 편집 영역은 contenteditable로 열어두되, 굵게나 목록 같은 서식 조작은 에디터가 직접 처리해요. 사용자가 굵게 버튼을 누르면, 에디터가 지금 어디가 선택됐는지를 알아내서 그 부분만 정확히 바꾸는 식이죠.
이때 쓰는 도구가 Selection(선택 영역)과 Range(범위)예요. 화면에서 커서가 어디 있는지, 어디부터 어디까지 드래그됐는지를 이 둘로 읽어와요. 브라우저의 제멋대로 동작에 맡기는 대신, 에디터가 내 문서가 지금 이렇다는 그림을 직접 들고 있다가 원하는 대로 고치고 화면에 다시 그려요. 이 방식을 모델 기반(model based, 데이터 중심)이라고 불러요. 잘 만든 라이브러리는 전부 이 길을 택했어요.
비유하면 이래요. contenteditable에 전부 맡기는 건 남이 운전하는 차 뒷자리에 앉는 거예요. 편하지만 어디로 갈지 내 맘대로 못 하죠. 모델 기반은 내가 운전대를 잡는 방식이에요. 손은 더 가지만, 굵게 하나를 넣어도 정확히 원하는 결과가 나와요. 그래서 서식이 조금만 복잡해져도 결국 이 길로 가게 돼요.
15.6 리치 에디터를 만들 때 뭐가 그렇게 어렵나요?
겉보기엔 버튼 몇 개인데 왜 어렵냐면, 경우의 수가 폭발하기 때문이에요. 굵은 글자 중간에 커서를 두고 새 글자를 치면 이어서 굵어야 할까요. 목록 항목 맨 앞에서 백스페이스를 누르면 목록이 풀려야 할까요. 다른 곳에서 복사한 글을 붙여넣으면 남의 서식이 딸려 들어와요. 이런 상황이 수백 가지예요.
게다가 브라우저마다, 한글 같은 조합 문자 입력(IME)까지 얽히면 동작이 또 달라져요. 그래서 제대로 된 에디터를 맨손으로 만드는 건 정말 큰 일이에요. 대부분은 검증된 라이브러리를 골라 쓰고, 필요한 만큼만 손보는 쪽을 택해요. 무엇을 어떻게 고르는지는 뒤에서 하나하나 비교해요.
실제로 큰 서비스들도 이 편집기 하나에 전담 인력을 두거나 몇 년째 다듬고 있어요. 그만큼 끝이 없는 부품이라는 뜻이죠. 그러니 처음부터 완벽하게 못 만든다고 주눅 들 필요 없어요. 원리를 이해하고, 잘 만든 도구를 영리하게 골라 쓰는 것도 엄연한 실력이에요.
15.7 오늘 정리
오늘은 리치 에디터가 무엇인지 큰 그림을 잡았어요. 리치 에디터는 글과 서식을 한 덩어리로 다루는 편집기고, 순수 텍스트만 담는 textarea로는 불가능했죠. 브라우저는 contenteditable 속성으로 아무 영역이나 편집창으로 바꿔주고, 그 안에서는 서식이 진짜 HTML 구조로 남았어요. 예전엔 execCommand로 서식을 넣었지만 지금은 폐기돼 믿기 어렵고, 요즘 에디터는 Selection과 Range로 상태를 직접 들고 다루는 모델 기반으로 움직인다고 했어요. 그리고 경우의 수가 워낙 많아 맨손 제작은 험난하다는 것까지요. 이 큰 그림을 깔았으니, 이제 그 밑바닥인 contenteditable이 왜 함정투성이인지 들여다볼 차례예요.