One self-contained HTML file. No accounts, no cloud, no watermark, no build step.
A from-scratch infinite whiteboard in the spirit of Miro, built for personal use and math teaching: pressure ink with smart shape snapping, LaTeX math blocks, function plots, images, sticky notes — and full PDF import: drop in a PDF and every page lands on the canvas as a high-fidelity image — the design stays exactly as-is. Optionally add an editable text layer on top, and export it all back out as a PDF.
Current build: slate v0.8 — PDF import · single canvas · PDF export
Open slate.html in any modern browser. That's the whole install.
- Boards auto-save to the browser (localStorage). Imported PDFs are stored as the original file once (compressed) and re-rendered on load — so even big multi-page PDFs stay well under the storage quota and survive a refresh.
- Export → PDF downloads an A4 PDF — one page per imported PDF page, and any other content is split into readable A4 pages instead of being squashed onto a single page (same jsPDF engine as before).
- Export → Import PDF (.pdf) — or just drag & drop a PDF — imports every page stacked vertically, pixel-for-pixel so nothing overlaps or changes. Each page is an image object you can move/scale/delete. Prefer the Import PDF + editable text option if you want real, double-click-editable text objects on top.
- Works offline except three CDN loads: MathJax (math blocks), jsPDF (PDF export) and pdf.js (PDF import). Everything else — drawing, plots, PNG/JSON export — is fully offline.
Tip: if you host it in a sandboxed iframe (e.g. an embed that blocks downloads and storage), Slate detects that automatically: exports switch to an in-app overlay (right-click to save / Print → Save as PDF) and boards run in-memory with an amber "preview" indicator. In a normal tab everything is one-click.
| Tool | Key | Tool | Key | |
|---|---|---|---|---|
| Select | V |
Text | T |
|
| Hand / pan | H |
Sticky note | N |
|
| Pen | P |
Code block | C |
|
| Highlighter | L |
Math (LaTeX) | M |
|
| Eraser | E |
Image | I (or paste / drag-drop) |
|
| Rectangle | R |
Function plot | X |
|
| Ellipse | O |
Arrow | A |
- PDF import: Export menu → Import PDF (.pdf) (exact design, image-only) or Import PDF + editable text (adds a text layer on top), or drag & drop a PDF onto the canvas. Pages stack vertically; imported PDFs export back out through Export → PDF with one exported page per imported page — annotations stay on their page.
- Smart snap: draw a rough line / circle / rectangle and hold still for a beat before releasing — Slate replaces it with a clean shape. Release quickly to keep freehand.
- Plots: press
X, type e.g.x^2 - 3,sin(x)*x,1/x. Safe hand-rolled expression compiler (noeval):sin cos tan asin acos atan sqrt abs ln log exp floor ceil sinh cosh tanh, constantspie, powers^, implicit multiplication (2x,3sin(x)). Double-click a plot to edit. - Undo/redo
Ctrl+Z/Ctrl+Y· duplicateCtrl+D· copy/pasteCtrl+C/V· zoomCtrl+wheel,Ffit · gridG· snap-to-gridS· themeD· hide UIU. - Double-click any text / note / code / math / plot / imported PDF text to edit it.
Boards persist as JSON (also what Export → Backup gives you):
Object types: s stroke (pts: [[x,y,pressure]], hl: true = highlighter) · r
rect · o ellipse · a arrow · t text · n note · c code · m math
(latex) · i image (src data-URL, pdf: true on imported PDF pages) · p
plot (expr, xmin, xmax).
Legacy backups migrate automatically: v1 ({ name, objects, cam }) loads as-is;
v2 notebooks ({ pages: [...] }) flatten onto the single canvas, pages stacked
with a gap so nothing overlaps.
Headless-browser end-to-end suites (Playwright, Chromium):
npm i playwright-core
npx playwright install chromium-headless-shell # once
# serve the folder, then:
python3 -m http.server 8900 &
node tests/slate-e2e.mjs http://localhost:8900/slate.html # 45+ checks
node tests/slate-sandbox-test.mjs http://localhost:8900/slate.html # blocked-storage env
node tests/slate-pdf-repro.mjs http://localhost:8900/slate.html # sandboxed-iframe exportsslate-e2e.mjs covers: drawing/shapes/text/math, select-move-undo-redo,
localStorage persistence, PNG + PDF byte verification, v0.6 features (highlighter,
image insert, plot compiler, smart snap), and v0.8 (single canvas, full PDF
import image-only default + opt-in text layer, v1/v2 → v3 migration, paginated
PDF export with one page per imported page).
slate-v7-shot.mjs recreates the showcase screenshot.
- Rendering: single
<canvas>, variable-width filled-outline ink (perfect-freehand technique, hand-rolled), scene-snapshot fast path while drawing (O(1) per frame regardless of board size). - Math: MathJax tex-svg → SVG → cached bitmaps, so equations stay crisp at any zoom.
- PDF import: pdf.js renders each page to a canvas (white-backed image object) so the
design is preserved exactly. The opt-in text layer extracts runs into native
tobjects — position-approximate but fully editable — kept off by default because overlay text can never align perfectly with the raster. - Storage: boards save the original PDF bytes once (deflate-compressed), not one JPEG per page, and re-render pages after a reload — fixing the old "Storage full → PDF gone after refresh" failure.
- Everything lives in
slate.html— readable, commented, ~1,875 lines.
{ "v": 3, "name": "Calculus notes", "objects": [ /* ... */ ], // single canvas — no notebook pages "cam": { "x": 0, "y": 0, "z": 1 } }