Skip to content

필사 모드: 웹 에디터에 자가 없는 진짜 이유 — 잴 자가 없는 게 아니라 잴 종이가 없습니다

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

워드에서 붙여 넣은 문서가 흐트러지는 지점

사내 문서 도구를 웹으로 옮기는 프로젝트에서 거의 반드시 나오는 요구가 있습니다. 워드처럼 눈금자를 달아 달라는 것입니다. 문단마다 왼쪽 여백을 삼각형으로 끌어 맞추고, 첫 줄만 들여쓰고, 표의 열 경계를 눈금 위에서 잡는 그 도구입니다.

개발 쪽에서는 이 요구가 사소해 보입니다. 자 모양의 눈금을 하나 그리고 드래그 핸들을 얹으면 될 것 같기 때문입니다. 그런데 착수하고 나면 첫날에 막힙니다. 눈금에 3cm라고 쓰려면 화면의 몇 픽셀이 3cm인지 알아야 하는데, 그 답이 없습니다.

아무도 안 만든 게 아닙니다

이건 만드는 사람이 없어서 비어 있는 자리가 아닙니다. 최근 공개된 editor-ruler의 README는 이 상황을 이렇게 정리합니다. 고전적인 웹 위지윅 에디터들, 그러니까 Froala와 TinyMCE와 CKEditor 5와 Quill 같은 것들은 눈금자 없이 나오고, 눈금자가 있는 것은 Syncfusion이나 DevExpress나 ONLYOFFICE처럼 문서 모델을 통째로 들고 있는 무거운 컴포넌트들뿐이라는 것입니다.

이 분류가 힌트입니다. 눈금자가 있는 쪽과 없는 쪽을 가르는 것은 개발 인력이나 제품 성숙도가 아니라 문서 모델을 갖고 있는지 여부입니다.

웹에는 페이지가 없습니다

워드에서 눈금자가 성립하는 이유는 종이가 먼저 정해져 있기 때문입니다. A4를 고르면 폭이 210mm로 확정되고, 좌우 여백이 각각 25mm면 본문 폭은 160mm입니다. 눈금자는 이 160mm를 그리는 자이고, 삼각형을 3cm 위치로 끌면 그것은 종이 왼쪽 끝에서 3cm라는 뜻입니다. 화면 크기를 바꿔도 이 숫자는 변하지 않습니다. 종이는 화면과 무관하게 존재하기 때문입니다.

웹에는 그 종이가 없습니다. 편집 영역의 폭은 브라우저 창 크기와 사이드바 상태와 확대 배율에 따라 계속 변합니다. 그리고 브라우저의 물리 단위는 실제 물리 길이가 아닙니다. CSS에서 1인치는 96픽셀로 정의된 값이고, 그 96픽셀이 화면에서 실제로 몇 밀리미터인지는 모니터마다 다릅니다.

그래서 웹 에디터에 눈금자를 그린다는 것은 없는 종이를 하나 가정하는 일입니다. 문제는 자를 그리는 기술이 아니라, 무엇을 기준으로 잴지 정하는 것입니다.

콘텐츠 폭을 종이로 삼는다는 선택

editor-ruler가 택한 방식은 코어 API에 그대로 드러납니다. 사용하는 쪽이 현재 상태를 알려 주는 함수를 넘기게 되어 있습니다.

import { createRuler } from '@devslab/editor-ruler';

const ruler = createRuler(mountElement, {
  unit: 'cm',
  getMetrics: () => ({ contentWidth, leftMargin, rightMargin, firstLineIndent }),
  onChange(change, phase) {
    // 현재 문단에 픽셀 값을 적용한다. 놓는 순간 phase 가 commit 이 된다
  },
});
ruler.refresh(); // 선택이나 내용이 바뀔 때마다 호출

여기서 읽어야 할 것은 첫 번째 인자가 아니라 getMetrics입니다. 눈금자는 종이 폭을 스스로 알지 못하고, 매번 호스트에게 물어봅니다. 즉 종이 역할을 하는 것은 편집 영역의 현재 콘텐츠 폭이고, 그 폭이 바뀌면 눈금자의 숫자도 다시 계산됩니다.

이 선택에는 대가가 있습니다. 화면을 좁히면 눈금자의 3cm 위치가 실제로 다른 곳을 가리키게 됩니다. 그래서 이 자는 워드의 자와 이름만 같을 뿐 다른 것을 재고 있습니다. 워드의 자는 인쇄될 종이를 재고, 이 자는 지금 화면에 잡힌 편집 영역을 잽니다. 팀에 도입할 때 사용자에게 반드시 알려야 하는 차이입니다.

코어와 어댑터로 나눈 구조

이 프로젝트는 눈금과 드래그 처리와 단위 환산만 담당하는 의존성 없는 코어를 두고, 에디터별 차이는 얇은 어댑터로 분리했습니다. Froala, Tiptap, CKEditor 5, Summernote 어댑터가 각각 별도 패키지로 나와 있습니다.

이 구조가 필요한 이유는 에디터마다 들여쓰기를 저장하는 자리가 다르기 때문입니다. Tiptap 어댑터는 들여쓰기를 노드 속성으로 저장하고 인라인 CSS로 렌더링합니다. CKEditor 5 어댑터는 모델 속성으로 저장하고 다운캐스트 단계에서 인라인 CSS로 바꿉니다. Froala와 Summernote 어댑터는 DOM을 직접 건드립니다. 눈금자가 하는 계산은 같은데, 그 결과를 어디에 쓸 것인지가 넷 다 다릅니다.

여기서 앞의 논지가 다시 나옵니다. 눈금자를 붙인다는 것은 문단의 여백을 어디에 저장할지 결정하는 일입니다. 노드 속성인지 인라인 스타일인지에 따라 붙여넣기와 내보내기와 협업 편집의 동작이 전부 달라집니다. UI 부품 하나를 얹는 결정이 아닙니다.

인쇄가 끼어들면 종이가 다시 생깁니다

여기서 결정이 갈리는 두 번째 지점이 나옵니다. 이 문서가 화면에서만 읽히면 종이는 끝까지 없습니다. 그런데 대부분의 사내 문서 도구는 결국 인쇄되거나 PDF로 나갑니다. 그 순간 종이가 다시 생깁니다.

인쇄 경로가 있다면 눈금자가 재야 하는 것은 편집 영역의 폭이 아니라 인쇄될 종이의 본문 폭입니다. 웹에서 그 종이를 선언하는 자리는 CSS의 @page 규칙이고, 여기서 용지 크기와 여백이 정해집니다. 즉 눈금자의 기준을 정하는 일은 인쇄 스타일시트의 값과 같은 값을 보게 만드는 일이 됩니다.

이 둘을 섞으면 가장 나쁜 결과가 나옵니다. 사용자가 화면 기준으로 3cm를 맞춰 놓고 인쇄하면 종이에서는 3cm가 아닙니다. 그러면 눈금자가 있어서 오히려 신뢰를 잃습니다. 그래서 선택지는 사실상 둘입니다. 편집 영역을 고정 폭의 가짜 종이로 만들어 화면과 인쇄를 일치시키거나, 눈금자가 재는 것이 인쇄 결과가 아니라는 점을 UI에서 분명히 말하거나. 둘 다 하지 않는 것이 지금 많은 도구가 눈금자를 넣지 않는 이유이기도 합니다.

만들어 보면 걸리는 자잘한 것들

이 프로젝트의 README에는 실제로 만들어 본 사람만 적을 수 있는 항목이 몇 개 있습니다. 두 가지가 특히 실무적입니다.

첫째, 되돌리기의 단위입니다. 드래그 한 번은 수십 번의 값 변경을 만들어냅니다. 그대로 두면 되돌리기를 스무 번 눌러야 원래 위치로 돌아옵니다. 이 프로젝트는 드래그 한 번을 되돌리기 한 단계로 묶는다고 명시합니다. 앞의 API에서 phase가 커밋인지 구분되는 이유가 이것입니다.

둘째, 세로 눈금자를 켜고 끌 때의 레이아웃 흔들림입니다. 세로 자가 나타나면 그만큼 본문이 밀립니다. 이 프로젝트는 자리를 미리 비워 두는 옵션을 두고, 그 동작을 CSS의 scrollbar-gutter: stable에 빗대어 설명합니다. 이 비유는 문제의 성격을 정확히 짚습니다. 나중에 나타날 수 있는 UI는 나타나기 전에 자리를 잡아 두어야 한다는 것입니다.

이 프로젝트의 현재 상태를 있는 그대로 적으면

과장할 수 없는 부분을 분명히 하겠습니다. 이 저장소는 만들어진 지 며칠 되지 않았고, 스타 수는 한 자릿수이며, 개인 또는 아주 작은 팀이 만든 것입니다. 라이선스는 Apache-2.0이고 언어는 TypeScript이며 npm 패키지가 올라와 있습니다. README에는 1.0.0부터 안정적이라는 문구가 있는데, 이것은 제3자의 평가가 아니라 만든 사람 본인의 선언입니다.

그래서 저는 이 글에서 이 라이브러리를 쓰라고 권하지 않습니다. 프로덕션 도입 여부는 직접 검증할 문제입니다. 이 프로젝트가 지금 시점에 볼 만한 이유는 다른 데 있습니다. 문제를 정확히 서술한 문서가 붙어 있기 때문입니다. 저자는 페이지 모델이 없을 때 눈금자가 무엇을 의미할 수 있는지, 만들면서 걸린 것이 무엇인지를 따로 글로 정리해 두었습니다. 직접 만들 계획이 있든 없든, 그 문제 정의는 읽을 값이 있습니다.

정리와 출처

웹 에디터에 눈금자가 없는 것은 기능이 빠진 것이 아니라 전제가 없는 것입니다. 종이를 정하지 않으면 잴 수 없고, 종이를 정하는 순간 그것은 UI가 아니라 문서 모델의 결정이 됩니다. 사내 도구에 이 기능을 요구받았다면, 구현을 견적 내기 전에 무엇을 종이로 삼을지부터 합의하시기 바랍니다.

  • devslab-kr/editor-ruler 저장소 — 코어 API, 어댑터별 저장 방식, 되돌리기 처리, 세로 자 자리 예약 옵션. 본문의 이 프로젝트 관련 서술은 모두 README에서 확인했습니다.
  • Every web rich-text editor is missing a ruler — 저장소가 배경 글로 연결해 둔 저자의 글
  • 저장소 메타데이터로 확인한 사항: 라이선스 Apache-2.0, 언어 TypeScript, 저장소 생성 시점과 스타 수. 성숙도에 대한 서술은 이 수치들에 근거한 것이며 코드 품질을 평가한 것이 아닙니다.
  • CSS에서 1인치가 96픽셀로 정의된다는 점은 CSS Values and Units 명세의 절대 길이 단위 정의에 따른 것입니다.

현재 단락 (1/34)

사내 문서 도구를 웹으로 옮기는 프로젝트에서 거의 반드시 나오는 요구가 있습니다. 워드처럼 눈금자를 달아 달라는 것입니다. 문단마다 왼쪽 여백을 삼각형으로 끌어 맞추고, 첫 줄만 ...

작성 글자: 0원문 글자: 3,610작성 단락: 0/34