drawably: Handgetekende UI-bedieningselementen
De bibliotheek heeft geen afhankelijkheden, bevat ongeveer 9 KB aan gzipped JS (React-wrappers voegen minder dan 1 KB toe) en een stylesheet van 3 KB. Er is een optioneel pen-lettertype beschikbaar van 31 KB.
Installatie
Installeer de bibliotheek via npm:
npm i drawably
Snelstart
Vanilla JavaScript
import { drawablyButton } from "drawably";
import "drawably/style.css";
drawablyButton(document.querySelector("#done"), { variant: "solid" });
React
import { DrawablyButton } from "drawably/react";
import "drawably/style.css";
<DrawablyButton variant="solid" onClick={submit}>Done</DrawablyButton>
Elke attach-aanroep retourneert een sketch-handle:
const sketch = drawablyButton(el);
sketch.resketch(); // Teken opnieuw met een nieuwe willekeurige seed
sketch.resketch(42); // Teken opnieuw met een specifieke seed
sketch.destroy(); // Verwijder de SVG en alle listeners
Knoppen
Er zijn drie varianten beschikbaar: outline (standaard), solid en scribble. Knoppen beschikken daarnaast over een state machine voor asynchrone processen:
const button = drawablyButton(el);
button.setState("loading"); // Dimt de knop, het 'boil'-effect versnelt
button.setState("error"); // Tekent de knop opnieuw in het rood
button.setState("success"); // Tekent de knop opnieuw in het groen
button.setState("idle");
In React kun je de state prop doorgeven; de schets blijft behouden en alleen de status verandert:
<DrawablyButton state={saving ? "loading" : "idle"}>Save</DrawablyButton>
Je kunt de kleuren voor de status overschrijven met --drawably-error en --drawably-success. Voor secundaire acties of destructieve acties kun je de tone instellen op "neutral" (warm grijs) of "danger" (rood).
Bedieningselementen
| Functie | Verwacht element |
|---|---|
drawablyButton(el, opts) | een <button> |
drawablyCheckbox(el, opts) | wrapper met <input type="checkbox"> |
drawablyRadio(el, opts) | wrapper met <input type="radio"> |
drawablyToggle(el, opts) | wrapper met <input type="checkbox"> |
drawablyInput(el, opts) | wrapper met een <input> |
drawablyTextarea(el, opts) | wrapper met een <textarea> |
drawablySelect(el, opts) | wrapper met een <select> |
drawablyDivider(el, opts) | een <hr> of div |
drawablyCard(el, opts) | elk block-element |
drawablyBadge(el, opts) | elk inline-element |
drawablyList(el, opts) | een <ul> of <ol>; elke <li> krijgt een getekende marker |
Aanvullende details over elementen
- Badges accepteren de variant:
"outline"|"scribble". - Lijsten accepteren de marker:
"dash"|"check". - Selects reserveren de breedte van de breedste optie, zodat de layout niet verschuift bij het selecteren. In Chromium krijgt de optielijst een getekend frame en een pen-vinkje (
appearance: base-select); Safari en Firefox behouden het OS-popupmenu.
De eigenlijke inputvelden blijven in de DOM aanwezig, waardoor toetsenbordnavigatie, formulieren, labels en screenreaders gewoon blijven werken. De schets is een aria-hidden SVG die eronder is geplaatst.
Elk element heeft een React-tegenhanger in drawably/react: DrawablyButton, DrawablyCheckbox, DrawablyRadio, DrawablyToggle, DrawablyInput, DrawablyTextarea, DrawablySelect, DrawablyDivider, DrawablyCard, DrawablyBadge, DrawablyList.
Tekstdecoratie
Hiermee kun je tekst annoteren alsof je een pen gebruikt. Elke decoratie koppelt aan een inline-element en laat de layout ongewijzigd; gebruik ze voor een woord of een korte zin.
| Functie | Resultaat |
|---|---|
drawablyUnderline(el, opts) | een ruwe lijn onder de tekst, wordt opnieuw getekend bij hover |
drawablyHighlight(el, opts) | een markeerstift-effect achter de tekst |
drawablyCircle(el, opts) | een handgetekende ellips rondom de tekst |
drawablyArrow(from, to, opts) | een pijl van het ene element naar het andere |
Voorbeeld in React:
import { DrawablyUnderline, DrawablyHighlight, DrawablyCircle, DrawablyArrow } from "drawably/react";
<p>
<DrawablyUnderline>Hand-drawn</DrawablyUnderline> UI, a{" "}
<DrawablyHighlight>fresh sketch</DrawablyHighlight> on{" "}
<DrawablyCircle>every mount</DrawablyCircle>.
</p>
<DrawablyArrow from={noteRef} to={buttonRef} />
Een decoratie die over meerdere regels loopt, krijgt per regel één tekening. De SVG van de pijl wordt toegevoegd aan de <body> in documentcoördinaten en wordt opnieuw getekend bij het veranderen van de venstergrootte. Ankers in een scrollende container zullen mee-bewegen tijdens het scrollen.
Composities
Dit zijn complexere componenten die zijn opgebouwd uit de bovenstaande elementen. Eén seed reproduceert elke lijn in het geheel; destroy() verwijdert alle elementen.
| Functie | Markup | Resultaat |
|---|---|---|
drawablyChip(el, opts) | <label><span><input type="checkbox"></span> text</label> | badge rond de chip, getekend kader om de input-wrapper |
drawablyTabs(el, opts) | children zijn de tabs | onderstreping bij de actieve tab; active index of aria-selected="true"; setActive(i) |
drawablyTooltip(tip, target, opts) | twee elementen | card rond de tip, pijl naar het doel |
drawablyAlert(el, opts) | optioneel [data-tag] child | card rond de alert, badge op de tag |
drawablySteps(el, opts) | <ol> | lijst met vinkjes |
drawablyKbd(el, opts) | <kbd> | badge met een strakkere lijn |
drawablyQuote(el, opts) | eerste element child, optionele <footer> | highlight op de regel, divider bij de footer |
drawablyPager(el, opts) | child <button>s | omlijnde pagina's, solide voor de huidige; active index of aria-current; setPage(i) |
Voorbeeld in React:
import { DrawablyChip, DrawablyTabs, DrawablyTooltip, DrawablyAlert, DrawablySteps, DrawablyKbd, DrawablyQuote, DrawablyPager } from "drawably/react";
<DrawablyChip defaultChecked>pen</DrawablyChip>
<DrawablyTabs active={tab}><span>a</span><span>b</span></DrawablyTabs>
<DrawablyTooltip to={buttonRef}>undo</DrawablyTooltip>
<DrawablyAlert><span data-tag>new</span> Import from Attio lands Friday</DrawablyAlert>
<DrawablySteps><li>record</li><li>label</li></DrawablySteps>
<DrawablyKbd>⌘K</DrawablyKbd>
<DrawablyQuote><span>less, but better</span><footer>Rams</footer></DrawablyQuote>
<DrawablyPager active={page}><button>‹</button><button>1</button><button>›</button></DrawablyPager>
Opties
Alle controls accepteren dezelfde basisopties:
| Optie | Standaard | Functie |
|---|---|---|
seed | random | Weglaten voor een unieke schets per mount, of een getal doorgeven voor een reproduceerbare schets |
roughness | 1 | De 'wiebel' van de basis-schets |
boil | 0.3 | Aantal pixels aan frame-tot-frame flikkering; 0 rendert één statisch pad |
stroke, fill, paper | pen blue / white | Kleuren, in te stellen via --drawably-* custom properties |
width | 2 | Lijndikte in pixels |
De kleuren zijn eenvoudige CSS custom properties, waardoor een thema ze in één keer kan instellen:
:root {
--drawably-stroke: #1a1a1a;
--drawably-fill: #1a1a1a;
}
Het lettertype is Inter wanneer dit op de pagina geladen is, anders wordt er teruggegrepen op system-ui. De bibliotheek laadt zelf geen lettertype, tenzij je expliciet kiest voor de optie hieronder.
Beweging
Lijnen 'koken' (boil) zachtjes: drie frames van dezelfde schets, met minimale variaties rond een gemeenschappelijke basis, gecycled via pure CSS over een periode van 1200ms. Hoveren of klikken triggert een nieuwe schets voor knoppen, checkboxes, radios, toggles, onderstrepingen en cirkels; knoppen komen bij hover omhoog en zakken bij een klik naar beneden. prefers-reduced-motion bevriest alles tot één statische schets.
Lettertype (optioneel)
Drawably Pen is een lettertype dat dezelfde lijnen gebruikt als de UI-elementen (a–z, A–Z, cijfers en interpunctie), gebouwd met de pen-code van de bibliotheek (font/) in een TrueType-bestand van 31 KB. Het wordt niet geladen door style.css en is niet vereist voor de werking van de bibliotheek.
Als je labels wilt in dezelfde stijl als de UI-elementen:
import "drawably/font.css";
.drawably-button {
font-family: "Drawably Pen", Inter, sans-serif;
}
Eigen vormen bouwen
De ruwe renderer is geëxporteerd. Elke functie retourneert een SVG-padstring, en variants produceert de frames voor het boil-effect:
import { roughRoundedRect, roughLine, roughCircle, variants } from "drawably";
const frames = variants(
(o) => roughRoundedRect(0, 0, 200, 100, 12, o),
{ seed: 7, roughness: 1, boil: 0.3 },
);
// Geeft drie path-strings terug — render deze en cycle de opacity
Ook geëxporteerd zijn: roughEllipse, roughArrow, roughCheckmark, scribbleFill, en de seeded PRNG mulberry32 met randomSeed.
Licentie
MIT.
Groetjes,