Materializr
Open-source parametric 3D CAD for makers — constraint sketches, solid modeling, threads, SVG & text engraving, STL/STEP export. Android, iPad, Linux, Mac, Windows. Vibe coded.
Framework-agnostic handwriting (digital ink) editor for canvas: pressure strokes, vector eraser, undo.
Translation pending
DesignHTML · TypeScriptGitHub @KimilheeOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
캔버스 하나를 손글씨(digital ink) 에디터로 만드는 프레임워크 비의존 라이브러리. dk-script의 수식 필기 영역을 떼어낸 것이고, 인식·변환은 범위 밖이다. 시험용 앱 → kimilhee.github.io/dk-ink (펜/지우개·두께·되돌리기·획 JSON)Read the full README ↗
캔버스 하나를 손글씨(digital ink) 에디터로 만드는 프레임워크 비의존 라이브러리.
dk-script의 수식 필기 영역을 떼어낸 것이고, 인식·변환은 범위 밖이다.
시험용 앱 → kimilhee.github.io/dk-ink (펜/지우개·두께·되돌리기·획 JSON)
PointerEvent.pressure를 굵기로 반영한다. 캔버스의 lineWidth는 획 하나에
하나뿐이라 점마다 굵기를 못 주므로, 윤곽을 만들어 채운다.dynamicEraser를 켜면 지우개를 빠르게 문지를수록 지름이 커지고(20~180px)
느리게 움직이면 작아진다. 켜면 eraserWidth는 쓰지 않는다.getCoalescedEvents()까지 풀어서 궤적이 각지지 않게 한다.desynchronized 캔버스 힌트로 합성 대기를 줄인다. 예측 점
(getPredictedEvents())은 꺾는 곳에서 선이 튀어 쓰지 않는다.ResizeObserver로 자동 추적한다.onChange가 현재 획 전체를 준다. 그대로 저장하거나
보내면 된다.의존성은 없다. 브라우저 API만 쓴다.
pnpm add dk-ink
import { createInkEditor } from "dk-ink";
const editor = createInkEditor(document.querySelector("canvas")!, {
tool: "pen",
strokeWidth: 3,
widthMode: "pressure",
onChange(strokes) {
console.log(strokes.length);
},
});
editor.tool = "eraser";
editor.setStyle({ eraserWidth: 32 });
editor.undo();
const strokes = editor.getStrokes(); // JSON으로 저장 가능
editor.setStrokes(strokes); // 다시 불러오기
editor.destroy();
캔버스의 크기·테두리는 앱이 정한다. 동작에 필요한 CSS(touch-action: none 등)만
라이브러리가 캔버스에 직접 넣으므로 스타일시트를 따로 불러올 필요가 없다. 넣기 전의 인라인
값은 기억해 두었다가 destroy()에서 되돌리므로, 앱이 걸어둔 스타일을 덮어쓴 채 떠나지 않는다.
다 쓴 획과 막 끝나 "마르는" 획은 캔버스 바로 아래에 겹친 별도 캔버스 두 장에 그린다(캔버스 앞 형제로 DOM에 붙고
destroy()에서 떼어진다). 그래서 배경은 캔버스가 아니라 캔버스를 감싼 요소에 칠해야 한다.
캔버스에 배경을 칠하면 다 쓴 획이 가려진다. 캔버스가 position: static이면 아래 층보다 위에
오도록 position: relative가 들어간다.
현재 도구는 data-tool 속성으로 노출되니 canvas[data-tool="eraser"] { cursor: crosshair }
처럼 쓰면 된다.
InkPoint.t는 PointerEvent.timeStamp 그대로다 (performance.now()와 같은 시간 원점).
뭉쳐 들어온 점들도 각자 실제 샘플 시각을 가지므로, 획순과 필기 속도가 점 데이터에 남는다.
InkEditor| 멤버 | 설명 |
|---|---|
tool | "pen" | "eraser" | "line". 읽기·쓰기. |
drawing | 획이 진행 중인지. |
canUndo / canRedo | 버튼 활성 상태에 쓴다. |
getStrokes() | 내부 상태와 분리된 복사본. |
setStrokes(strokes, options?) | 통째로 교체. { recordHistory: false }면 되돌리기 이력을 건너뛴다. |
getStyle() / setStyle(partial) | 색·굵기·모드·지우개 설정. |
undo() / redo() / clear() | 바뀐 게 있으면 true. |
resize() | ResizeObserver가 부르므로 보통 직접 쓸 일이 없다. |
destroy() | 리스너·옵저버 해제, 이력 비움, 캔버스 스타일·data-tool을 부착 이전으로 복원. 그린 내용은 그대로. |
| 옵션 | 기본값 |
|---|---|
tool | "pen" |
color | "#1d4ed8" |
strokeWidth | 3 (필압 모드에선 최대) |
widthMode | "pressure" |
opacity | 0.7 |
holdToStraightLine | true (끝에서 멈추면 직선) |
straightLineDelay | 700 (대기 시간, ms) |
eraserWidth | 50 |
dynamicEraser | false (속도로 지름 결정) |
showEraserCursor | true |
eraserCursorFill / eraserCursorStroke | 청록 계열 |
historyLimit | 50 (0이면 되돌리기 없음) |
strokes | 초기 획 |
onChange | — |
펜으로 곧게 긋다가 끝에서 기본 0.7초간 멈추면(8px 이내 손떨림 허용) 시작점과 그 자리를 잇는
일정한 두께의 직선으로 바뀐다. 획이 16px 이상이고 모든 점이 선분에서 길이의 8%(최소 6px) 안에
있을 때만 바뀌므로, 쓰기 전에 펜을 대고 머뭇거리거나 꺾인 획 끝에서 멈춰도 직선이 되지 않는다.
바뀐 뒤에도 펜을 떼기 전까지 끝점을 옮길 수 있다. 대기 시간은 straightLineDelay(ms)로 지정하며,
데모에서는 0.5~1.5초 슬라이더로 조절하고 저장한다. holdToStraightLine: false로 끌 수 있다.
tool = "line"이면 기다리지 않고 누른 자리에서 뗀 자리까지 직선을 긋는다. 데모 도구 모음의
직선 버튼이 이 도구를 켜고 끈다.
Stroke는 점 배열이 아니라 { points, style }이다. 획을 시작할 때 그 시점의 색·굵기·모드·
투명도를 복사해 넣으므로, 나중에 setStyle로 설정을 바꿔도 이미 그린 획은 그대로다 —
사람이 그때 그 펜으로 쓴 것이기 때문이다. 지우개로 쪼갠 조각도 원래 획의 스타일을 물려받는다.
type Stroke = { points: InkPoint[]; style: StrokeStyle };
type StrokeStyle = { color: string; strokeWidth: number; widthMode: WidthMode; opacity: number };
setStyle은 앞으로 그릴 획의 설정을 바꾼다. 이미 그린 획을 바꾸려면 getStrokes()로
받아 style을 고친 뒤 setStrokes()로 되돌려 넣으면 된다.
에디터를 쓰지 않고 직접 조립할 때 쓸 순수 함수도 내보낸다:
eraseStrokes · renderStrokes · drawStaticLineStroke · drawOutlineStroke ·
drawEraserCursor · pressureWidth · cloneStrokes · createInkHistory · createDynamicEraser.
createInkHistory의 스냅샷은 배열만 복사하고 획 객체를 공유한다. 넘긴 획을 나중에 고칠
거라면 cloneStrokes로 복사해서 넘긴다.
코드 읽기는 분석 커리큘럼에서 시작한다. 다이어그램은 브라우저에서 바로 열 수 있다:
vp install
vp run playground # 시험용 앱 (펜/지우개/두께/되돌리기/JSON 확인)
vp test
vp check
vp pack # dist/ 빌드
vp run archify:refresh # 최신 코드를 읽고 분석 문서 5개 재생성
playground/는 npm 배포물이 아니다. 라이브러리 소스를 직접 import하므로 수정이 바로 반영되고,
main에 푸시하면 GitHub Pages로 배포된다.
archify:refresh는 Codex CLI의 비대화형 실행을 사용한다.
로그인된 codex CLI, 설치된 archify 스킬, 브라우저 검사용 Chrome/Chromium이 필요하다.
AI가 현재 코드를 다시 분석하므로 실행 시간과 모델 사용량이 발생한다. 소스 변경을 먼저 커밋하면
문서의 근거 링크를 해당 커밋에 고정할 수 있다. 생성 원본과 검증 결과는 새 .archify/ 폴더에
보존하고(로컬에만 두고 커밋하지 않는다), 검증된 HTML 5개를 playground/public/analysis/에 반영한다.
이 명령은 로컬 파일과 patch 버전만 갱신한다. 변경을 검토하고 main에 푸시하면 Pages에 배포된다.
다이어그램 설명은 한국어이며 Viewer 기본 UI는 영어 fallback을 사용한다.