Material Lab
Full-page experience · Open playground
Explore seeded marble, paper, and cellular surfaces as backgrounds and repeating fills.
Try it
Section titled “Try it”Switch materials, cycle the palette, and make a new variation.
What to learn
Section titled “What to learn”- Reuse procedural texture output in Canvas composition.
- Keep texture generation independent of display size.
Integration boundaries
Section titled “Integration boundaries”- Uses the optional @pibbl/core/textures package and the public render-layer lifecycle.
- Texture renderer sources are borrowed for the synchronous callback; Pibbl owns presentation.
This is a Pibbl Canvas example. Its mount owns its runtime resources; call its controller’s dispose method when removing it. The preview starts automatically unless reduced motion is enabled; Stop disposes the mount. Preserve the source’s logical canvas size and aspect ratio. Native HTML is not captured by Canvas filters.
Related APIs
Section titled “Related APIs”Complete source
Section titled “Complete source”Host setup for this source: use a canvas with width 880 and height 630, compile with jsx: "react-jsx" and jsxImportSource: "@pibbl/core", import its default mount, and call const controller = mount(canvas) after attaching the canvas. Call controller.dispose() before removing it.
material-lab.example.tsx
Section titled “material-lab.example.tsx”/** @jsxImportSource @pibbl/core */import { pibbl, Group, Rectangle, Text, FocusManagement, useSignal, type PibblController,} from "@pibbl/core";import { useTexture, marble, paper, cellular, type TextureKind,} from "@pibbl/core/textures";
const palettes = [ ["#f2e8d7", "#c4b8a9", "#625e68", "#252c3f"], ["#091f26", "#24525a", "#88b8a5", "#e0dbb0"], ["#321c35", "#986780", "#e8b6a0", "#fff0cc"],];const kinds: readonly TextureKind[] = ["marble", "paper", "cellular"];function MaterialSwatches({ kind, seed, scale, detail, palette,}: { kind: TextureKind; seed: number; scale: number; detail: number; palette: number;}) { const factories = { marble, paper, cellular }; const texture = useTexture(factories[kind]({ seed, scale, detail }), { resolution: { width: 320, height: 240 }, palette: palettes[palette], }); return ( <> <Rectangle pointerEvents="none" style={{ width: 520, height: 370, fill: texture.paint({ mode: "stretch" }), }} /> <Rectangle pointerEvents="none" style={{ left: 540, width: 292, height: 370, cornerRadius: 18, fill: texture.paint({ mode: "repeat", tileWidth: 160, tileHeight: 120, }), }} /> <Rectangle pointerEvents="none" style={{ left: 560, top: 222, width: 252, height: 126, fill: "#14232cbb" }} /> <Rectangle pointerEvents="none" style={{ left: 18, top: 304, width: 350, height: 50, cornerRadius: 10, fill: "#14232cee", }} /> <Text pointerEvents="none" style={{ left: 32, top: 338, textBaseline: "alphabetic", font: "bold 28px Georgia", fill: texture, stroke: "#f4ead7", strokeWidth: 0.6, }} > One source. Real fills. </Text> </> );}function Button({ x, label, active, onClick,}: { x: number; label: string; active?: boolean; onClick: () => void;}) { return ( <Group style={{ translateX: x }}> <Rectangle keyboardFocusable onClick={onClick} style={{ width: 108, height: 36, cornerRadius: 18, fill: active ? "#edc9a1" : "#343541", cursor: "pointer", }} /> <Text pointerEvents="none" style={{ left: 54, top: 23, textBaseline: "alphabetic", textAlign: "center", font: "600 12px Arial", fill: active ? "#2b2831" : "#ece6dc", }} > {label} </Text> </Group> );}function MaterialLab() { const state = useSignal({ kind: "marble" as TextureKind, seed: 24, scale: 6, detail: 0.65, palette: 0, }); const s = state.get(); return ( <FocusManagement> <Group> <Rectangle pointerEvents="none" style={{ width: 880, height: 630, fill: "#20232e" }} /> <Text pointerEvents="none" style={{ left: 28, top: 30, textBaseline: "alphabetic", font: "600 11px Arial", fill: "#d6b798", }} > TEXTURE STUDIES / 02 </Text> <Text pointerEvents="none" style={{ left: 28, top: 70, textBaseline: "alphabetic", font: "600 34px Georgia", fill: "#f6ead5", }} > Material Lab </Text> <Text pointerEvents="none" style={{ left: 28, top: 95, textBaseline: "alphabetic", font: "13px Arial", fill: "#bcb9c5", }} > A little geology. A little paper. A reproducible accident. </Text> <Group style={{ translateX: 24, translateY: 118 }}> <MaterialSwatches key={s.kind} kind={s.kind} seed={s.seed} scale={s.scale} detail={s.detail} palette={s.palette} /> <Text pointerEvents="none" style={{ left: 580, top: 270, textBaseline: "alphabetic", font: "600 24px Georgia", fill: "#fff5df", }} > Made of texture. </Text> <Text pointerEvents="none" style={{ left: 580, top: 299, textBaseline: "alphabetic", font: "12px Arial", fill: "#e5d9c8", }} > Same source. Seamless repeat. </Text> <Text pointerEvents="none" style={{ left: 580, top: 325, textBaseline: "alphabetic", font: "11px Arial", fill: "#b8c6c2", }} > Ordinary Pibbl text on top. </Text> </Group> <Group style={{ translateX: 24, translateY: 510 }}> {kinds.map((kind, i) => ( <Button key={kind} x={i * 116} label={kind[0].toUpperCase() + kind.slice(1)} active={s.kind === kind} onClick={() => state.update((v) => ({ ...v, kind }))} /> ))} <Button x={364} label={`Scale ${s.scale}`} onClick={() => state.update((v) => ({ ...v, scale: v.scale === 6 ? 12 : v.scale === 12 ? 3 : 6, })) } /> <Button x={480} label={`Detail ${Math.round(s.detail * 100)}%`} onClick={() => state.update((v) => ({ ...v, detail: v.detail === 0.65 ? 1 : v.detail === 1 ? 0.25 : 0.65, })) } /> <Button x={596} label="Palette" onClick={() => state.update((v) => ({ ...v, palette: (v.palette + 1) % 3 })) } /> <Button x={712} label="New variation" onClick={() => state.update((v) => ({ ...v, seed: (v.seed + 1) >>> 0 })) } /> </Group> <Text pointerEvents="none" style={{ left: 28, top: 582, textBaseline: "alphabetic", font: "12px Arial", fill: "#d0c7bc", }} >{`${s.kind.toUpperCase()} / seed ${s.seed} / scale ${s.scale} / detail ${Math.round(s.detail * 100)}%`}</Text> <Text pointerEvents="none" style={{ left: 28, top: 606, textBaseline: "alphabetic", font: "11px Arial", fill: "#9396a6", }} > Static after generation. Change shape controls to regenerate; palette changes only recolor. </Text> </Group> </FocusManagement> );}export default function mount(canvas: HTMLCanvasElement): PibblController { return pibbl(canvas, <MaterialLab />);}Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.