drawably README

The project's README from github.com/danielwh2/drawably. As one markdown file: README.md.

drawably

Hand-drawn UI controls. Every mount generates a fresh pen sketch from seeded randomness, and the stroke boils like an animated doodle. Zero dependencies, ~7 KB of JS gzipped (React wrappers add under 1 KB) and a 3 KB stylesheet. An optional pen font is a separate 31 KB.

Buttons, checkbox, radio and toggle drawn in a boiling pen stroke

Install

npm i drawably

Quick start

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>

Each attach call returns a sketch handle:

const sketch = drawablyButton(el);
sketch.resketch();     // redraw with a new random seed
sketch.resketch(42);   // redraw with a specific seed
sketch.destroy();      // remove the SVG and all listeners

Buttons

Three variants: outline (default), solid, scribble. Buttons also carry a state machine for async work:

The four button states: idle, loading, error, success

const button = drawablyButton(el);
button.setState("loading");  // dims the button, boils faster
button.setState("error");    // redraws in red
button.setState("success");  // redraws in green
button.setState("idle");

In React, pass the state prop; the sketch stays put and only the state changes:

<DrawablyButton state={saving ? "loading" : "idle"}>Save</DrawablyButton>

Override the state colours with --drawably-error and --drawably-success. For secondary or destructive actions, set tone: "neutral" (warm grey) or tone: "danger" (red).

Controls

Function Element it expects
drawablyButton(el, opts) a <button>
drawablyCheckbox(el, opts) wrapper containing <input type="checkbox">
drawablyRadio(el, opts) wrapper containing <input type="radio">
drawablyToggle(el, opts) wrapper containing <input type="checkbox">
drawablyInput(el, opts) wrapper containing an <input>
drawablyTextarea(el, opts) wrapper containing a <textarea>
drawablySelect(el, opts) wrapper containing a <select>
drawablyDivider(el, opts) an <hr> or div
drawablyCard(el, opts) any block element
drawablyBadge(el, opts) any inline element
drawablyList(el, opts) a <ul> or <ol>; each <li> gets a sketched marker

Badges take variant: "outline" | "scribble"; lists take marker: "dash" | "check". Selects reserve the widest option's width so picking never shifts layout; in Chromium the options list gets a sketched frame and pen check (appearance: base-select), Safari and Firefox keep the OS popup.

The real inputs stay in the DOM, so keyboard, forms, labels and screen readers all work as usual. The sketch is an aria-hidden SVG layered underneath.

Every control has a React counterpart in drawably/react: DrawablyButton, DrawablyCheckbox, DrawablyRadio, DrawablyToggle, DrawablyInput, DrawablyTextarea, DrawablySelect, DrawablyDivider, DrawablyCard, DrawablyBadge, DrawablyList.

Text decoration

Annotate copy the way you would with a pen. Each attaches to an inline element and leaves its layout alone; use them on a word or a short phrase.

Function Draws
drawablyUnderline(el, opts) a rough line under the text, re-sketched on hover
drawablyHighlight(el, opts) a marker wash behind the text
drawablyCircle(el, opts) a hand-drawn ellipse looping around the text
drawablyArrow(from, to, opts) an arrow from one element to another
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} />

A decoration that wraps onto several lines gets one drawing per line. The arrow's SVG is appended to <body> in document coordinates and redraws on resize. Anchors inside a scrolling container will drift as it scrolls.

Options

All controls take the same base options:

Option Default What it does
seed random Omit for a unique sketch per mount, pass a number for a reproducible one
roughness 1 Wobble of the base sketch
boil 0.3 Px of frame-to-frame flicker; 0 renders one static path
stroke, fill, paper pen blue / white Colours, set as --drawably-* custom properties
width 2 Stroke width in px

The colours are plain CSS custom properties, so a theme can set them once:

:root {
  --drawably-stroke: #1a1a1a;
  --drawably-fill: #1a1a1a;
}

Type is Inter when the page has it loaded, falling back to system-ui. The library loads no font unless you opt into the one below.

Motion

Strokes boil gently: three frames of the same sketch, micro-wobbled around a shared base, cycled by pure CSS at 1200ms. Hover or press re-sketches buttons, checkboxes, radios, toggles, underlines and circles; buttons also lift on hover and sink on press. prefers-reduced-motion freezes everything to a single static sketch, including the demo images above.

Font (optional)

Drawably Pen is the same strokes as a typeface: a–z, A–Z, digits and punctuation, built by the library's own pen code (font/) into a 31 KB TrueType. It is not loaded by style.css; nothing in the library needs it. If you want labels in the same hand as the chrome:

import "drawably/font.css";
.drawably-button {
  font-family: "Drawably Pen", Inter, sans-serif;
}

Build your own shapes

The rough renderer is exported. Each function returns an SVG path string, and variants produces the boil frames:

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 },
);
// three path strings — render them and cycle opacity

Also exported: roughEllipse, roughArrow, roughCheckmark, scribbleFill, and the seeded PRNG mulberry32 with randomSeed.

License

MIT.

drawably (danielwh2/drawably)

Searching Database…

这是什么?Drawably 是一个 UI 组件库,以手绘、潦草的轮廓渲染按钮、输入框、复选框等控件。每次挂载都会依据带种子的随机数生成全新的钢笔风格素描,笔触还会像“沸腾的涂鸦”一样微微颤动。
这是原生 JS 库还是 React 框架?它是一个原生 JavaScript 和 CSS 库(JS 压缩后约 7 KB,外加 3 KB 样式表),为每个控件提供轻薄的 React 包装组件,因此既能配合纯 HTML 也能配合 React 使用。
怎么把它加进项目?安装软件包,导入样式表,然后在真实 DOM 元素上调用 drawablyButton(el, opts) 之类的附加函数,或者使用对应的 React 组件(如 DrawablyButton),它会替你完成附加操作。
使用它需要 React 吗?不需要,核心库零依赖,可直接附加到普通 DOM 元素;只有想使用可选的 React 包装组件时才需要 React。
下拉选择框在每个浏览器里都是手绘外观吗?只有 Chromium 能呈现完整的手绘外观,因为它支持 appearance: base-select 特性,能在选项列表内绘制手绘边框和钢笔勾选样式;Safari 和 Firefox 则回退为常规的系统下拉弹窗。
控件是用 SVG 绘制的,无障碍性还能保证吗?能,真实的表单输入仍保留在 DOM 中,键盘、标签和屏幕阅读器支持都在;手绘素描只是叠加在下层的 aria-hidden SVG。
我能每次都得到完全相同的素描,而不是随机的吗?可以,传入数字类型的 seed 选项而不是保持随机,就能复现相同的素描;还可以用 roughness 和 boil 来调节抖动和闪烁的程度。
有配套的手绘字体用于标签吗?有一个名为 Drawably Pen 的可选 31 KB TrueType 字体,用同样的笔触绘制字母、数字和标点,但它不会被自动加载,库中也没有任何部分依赖它。
我能用它构建自己的手绘图形吗?可以,rough 渲染器连同 roughEllipse、roughArrow、roughCheckmark、scribbleFill 等辅助函数以及带种子的伪随机数生成器 mulberry32 一起导出,你可以用它生成自定义的潦草 SVG 路径。
Creator
danielwh2
Size
0.4 MB
Stars
809
Updated
CommitSep 19
Age
2mo
Commits · 90 Days