StewardAI

Eunice · StewardAI

· 13분 읽기

Excalidraw 손그림 화이트보드가 작동하는 원리 (Rough.js)

Excalidraw의 손그림 화이트보드 효과는 폰트 트릭이 아니라, 선 하나하나를 매번 무작위로 다시 그리는 오픈소스 라이브러리 Rough.js 덕분입니다 — 실제 코드로 직접 확인한 내용입니다.

종이 위에 박스와 화살표로 손그림 스케치한 다이어그램 — Excalidraw가 디지털 캔버스에서 그대로 재현하는 느낌

대부분의 "손그림" 화이트보드 모드는 그냥 폰트를 바꾸는 것뿐입니다 — 사각형 위에 Comic Sans를 얹으면 끝입니다. Excalidraw는 그렇게 하지 않습니다. 캔버스 위의 모든 선, 박스, 채우기는 매번 기하학적 좌표를 조금씩 다르게 흔드는 작은 수학 라이브러리를 통해 다시 그려지기 때문에, 사각형 하나도 두 번 다시 똑같은 경로로 그려지지 않습니다. 이게 바로 Excalidraw의 손그림 화이트보드 효과가 실제로 작동하는 방식입니다 — 작은 트릭이지만 완전히 오픈소스이고, 코드에서 그대로 읽어낼 수 있습니다.

Excalidraw는 정확히 무엇인가

자체 README에 따르면 "오픈소스 가상 손그림 스타일 화이트보드. 협업 기능과 종단간 암호화를 지원한다."(An open source virtual hand-drawn style whiteboard. Collaborative and end-to-end encrypted.)라고 설명합니다. 메인 저장소는 스타 133.2k개, 포크 15.5k개를 보유하고 있으며, MIT 라이선스 아래 배포됩니다 — 무료로 사용, 포크, 상업적 임베드가 가능하고 카피레프트 조건이 붙지 않습니다.

이 마지막 부분이 중요한 이유는 실제로 많은 제품이 이 조건을 활용해왔기 때문입니다. README 자체의 "Who's integrating Excalidraw"(누가 Excalidraw를 통합하고 있는가) 섹션은 이렇게 나열합니다. "Google Cloud • Meta • CodeSandbox • Obsidian Excalidraw • Replit • Slite • Notion • HackerRank 등 다수"(Google Cloud • Meta • CodeSandbox • Obsidian Excalidraw • Replit • Slite • Notion • HackerRank • and many others). 이건 마케팅용 슬라이드가 아니라, 직접 스케치 스타일 렌더러를 처음부터 만드는 대신 이 특정 드로잉 엔진을 자사 UI에 그대로 심어 넣은 실제 제품들의 목록입니다.

메커니즘: 스케치는 그려지는 게 아니라 생성된다

손그림 느낌은 Excalidraw 자신의 코드에서 나오는 게 아닙니다. 의존성 하나에서 나옵니다. 바로 Rough.js이고, packages/excalidraw/package.json 매니페스트에 버전 4.6.4로 명시되어 있습니다. Rough.js 자체 README는 이렇게 담백하게 설명합니다. "Rough.js는 스케치풍, 손그림 같은 스타일로 그림을 그릴 수 있게 해주는 작은(9kB 미만) 그래픽 라이브러리다."(Rough.js is a small (<9 kB) graphics library that lets you draw in a sketchy, hand-drawn-like, style.) Rough.js는 별도의, 독립적으로 유지보수되는 오픈소스 프로젝트로, 자체 스타 21.2k개와 자체 MIT 라이선스를 갖고 있습니다.

실제 작동 방식은 이렇습니다. Excalidraw가 사각형을 렌더링해야 할 때, 깔끔한 사각형 하나를 그린 다음 그 위에 텍스처를 덮어씌우는 게 아닙니다. 도형의 좌표를 Rough.js에 넘기면, Rough.js가 통제된 무작위성을 적용해 경로를 다시 계산하고, 렌더링될 때마다 매번 처음부터 다시 그립니다.

Rough.js는 이 무작위성을 고정된 필터가 아니라 조절 가능한 파라미터로 노출합니다.

  • Roughness(거칠기) — 선이 "정확한" 기하학적 경로에서 얼마나 벗어나는지를 나타냅니다. 값이 낮으면 거의 직선처럼 보이고, 값이 높으면 낙서처럼 보입니다.
  • Bowing(휘어짐) — 원래 직선이어야 할 선에 곡률을 얼마나 더할지를 나타냅니다.
  • Fill style(채우기 스타일) — 일곱 가지 옵션이 있습니다. hachure(빗금 채우기, Rough.js의 기본값), solid, zigzag, cross-hatch, dots, dashed, zigzag-line입니다. 각 스타일은 자체 세부 설정도 노출합니다 — 예를 들어 hachure의 각도와 선 간격은 별도의 hachureAngle, hachureGap 파라미터로 조절합니다.

여기서 다른 곳에 옮겨 쓸 수 있는 아이디어가 나옵니다. Excalidraw는 손그림 폰트나 손그림 이미지 세트를 디자인하지 않았습니다. "사용자가 배치한 도형"과 "실제로 렌더링되는 경로"를 분리하고, 그 사이에 무작위화 엔진을 끼워 넣었습니다. 이 비정형성은 깔끔한 벡터 도형 위에 덧붙인 장식이 아니라, 그 도형이 그려지는 유일한 방식입니다.

이 구분이 훔쳐 갈 만한 가치가 있는 이유

정적인 "스케치" 스킨 — 흔들리는 SVG 필터나 떨리는 폰트 같은 것 — 은 처음 한 번은 손그림처럼 보이지만, 그 뒤로는 그 스킨으로 만드는 모든 다이어그램에서 완전히 똑같이, 똑같은 정도로만 흔들립니다. 여전히 템플릿인 셈입니다. Rough.js의 접근 방식 덕분에 Excalidraw의 다이어그램은 단 한 번도 통조림 같은 "스케치 효과" 하나로 고정되지 않습니다. 렌더링될 때마다 새로운 무작위성을 끌어오기 때문에, 자세히 들여다봤을 때 필터가 그렇듯 이 툴이 조용히 자기 반복을 드러내는 일이 없습니다.

완성되고 고정된 느낌보다는 비정형적이고 편안하며 "아직 진행 중"인 느낌을 주고 싶은 무언가를 만들고 있다면 — 와이어프레임 툴이든, 브레인스토밍 캔버스든, 노트 앱에 삽입되는 다이어그램이든 — 여기서 얻을 교훈은 "흔들리는 필터를 추가하라"가 아닙니다. Excalidraw가 실제로 건 승부수는 이것입니다. 무작위성을 에셋 라이브러리가 아니라 렌더링 단계 자체에 밀어 넣는 것.

무료 툴 아래 깔린 비즈니스 모델

Excalidraw는 기부금만으로 운영되지 않으며, README도 이를 직접적으로 밝힙니다. "이 프로젝트가 마음에 든다면 Open Collective에서 스폰서가 되거나 Excalidraw+를 이용할 수 있다."(If you like the project, you can become a sponsor at Open Collective or use Excalidraw+.) 이건 표준적인 투 트랙 오픈소스 모델입니다 — 누구나 셀프 호스팅하거나 임베드할 수 있는 무료 MIT 라이선스 코어(아마 Notion, Obsidian, Replit이 이런 식으로 쓰고 있을 것입니다)에, 직접 운영하기보다 비용을 지불하고 싶은 사람들을 위한 별도의 호스팅형 상업 제품 Excalidraw+가 더해지는 구조입니다. Excalidraw+의 마케팅 사이트는 GitHub 프로젝트와 다른 도메인에 있고, 이번 리서치 과정에서는 접속이 되지 않았기 때문에 이 글에서는 가격을 밝히지 않습니다.

솔직한 간극: 바쁜 저장소 옆에 멈춰 있는 태그

회의적으로 봐야 할 부분이 여기 있습니다. 가장 최근에 태그된 릴리스는 v0.18.1이고 날짜는 4월 21일입니다 — 이 글을 확인한 시점보다 몇 달 전입니다. 하지만 커밋 히스토리는 방치된 프로젝트와는 정반대의 모습을 보여줍니다. master 브랜치에는 9월 28일처럼 최근까지도 커밋이 올라오고 있고, 대부분의 날에 커밋이 여러 개씩 찍혀 있습니다. 저장소는 현재 열린 이슈 2.1k개를 안고 있고, 더 눈에 띄는 건 머지되지 않은 채 열려 있는 풀 리퀘스트 1,170개입니다.

종합해보면, 이 간극은 방치라기보다는 excalidraw.com에서 운영하는 자체 호스팅 앱에 계속해서 배포하면서 매번 그에 맞는 npm 태그를 새로 끊는 일은 딱히 신경 쓰지 않는 팀의 모습에 가깝습니다. 호스팅 제품 입장에서는 괜찮은 전략이지만, 셀프 호스팅 임베드를 위해 @excalidraw/excalidraw npm 패키지를 가져다 쓰는 사람이라면 최신 태그가 코드의 현재 상태를 반영한다고 가정하지 말고 자신이 고정한 실제 커밋을 확인해야 한다는 뜻이기도 합니다. 이는 사실상 개발자 한 명이 이끄는 오픈소스 프로젝트인 Aider와는 다른 형태의 거버넌스입니다 — Excalidraw는 손이 더 많지만, 그 손들이 아직 따라잡지 못한 적체도 함께 안고 있습니다.

공개된 조직 명부에 등록된 멤버는 12명 — 사람 11명에 봇 계정 1개 — 인데 여기에 풀 리퀘스트 1,170개가 쌓여 있다는 건, 직접 수정을 기여할지 아니면 포크해서 스스로 패치할지 고민하는 사람이라면 눈여겨봐야 할 정당한 유지보수 부담 신호이기도 합니다.

이게 일반화되면 어떤 이야기가 될까요

여기서 훔쳐 갈 아이디어는 "스케치처럼 보이게 만들자"가 아닙니다. 더 좁은 이야기입니다. 제품을 덜 완성된 느낌, 덜 위압적인 느낌, 덜 고정된 템플릿 느낌으로 만들고 싶다면 — Loop Habit Tracker의 감쇠하는 점수가 경직된 스트릭 카운터를 대체한 방식이나, Anki의 FSRS가 고정된 공식 하나를 카드별 모델로 대체한 방식처럼 — 그 느낌을 정적인 스킨으로 흉내 내지 마세요. 통제된 무작위성이나 적응성을 주입할 수 있는 계산 하나를 찾아내서, 그 손잡이들(roughness, bowing, fill; 기억 유지율; 감쇠율)을 하드코딩된 값이 아니라 파라미터로 노출하고, 렌더링이나 계산이 있을 때마다 그 작업을 매번 새로 하게 만드세요. Excalidraw의 "손그림" 이미지 전체는 스타 21.2k개, 9kB 미만인 의존성 하나가 사각형 하나하나마다 정확히 그 일을 해내는 데 달려 있습니다.

출처

아래 링크는 발행 전 직접 확인했습니다.

  1. 1.GitHub — excalidraw/excalidraw (메인 저장소) · 확인일
  2. 2.GitHub raw — Excalidraw README.md · 확인일
  3. 3.GitHub raw — packages/excalidraw/package.json (roughjs 의존성) · 확인일
  4. 4.GitHub — Excalidraw 최신 릴리스 (v0.18.1) · 확인일
  5. 5.GitHub — Excalidraw 커밋 히스토리 (master) · 확인일
  6. 6.GitHub — Excalidraw 열린 이슈 · 확인일
  7. 7.GitHub — Excalidraw 열린 풀 리퀘스트 · 확인일
  8. 8.GitHub — Excalidraw LICENSE (MIT) · 확인일
  9. 9.GitHub — excalidraw 조직 페이지 · 확인일
  10. 10.GitHub raw — rough-stuff/rough README.md · 확인일
  11. 11.GitHub — rough-stuff/rough (메인 저장소) · 확인일
  12. 12.npm 레지스트리 — @excalidraw/excalidraw 패키지 데이터 · 확인일

Read in English →