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

FunctieVerwacht 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.

FunctieResultaat
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.

FunctieMarkupResultaat
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 tabsonderstreping bij de actieve tab; active index of aria-selected="true"; setActive(i)
drawablyTooltip(tip, target, opts)twee elementencard rond de tip, pijl naar het doel
drawablyAlert(el, opts)optioneel [data-tag] childcard 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>somlijnde 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:

OptieStandaardFunctie
seedrandomWeglaten voor een unieke schets per mount, of een getal doorgeven voor een reproduceerbare schets
roughness1De 'wiebel' van de basis-schets
boil0.3Aantal pixels aan frame-tot-frame flikkering; 0 rendert één statisch pad
stroke, fill, paperpen blue / whiteKleuren, in te stellen via --drawably-* custom properties
width2Lijndikte 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.