pictoguys 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
  </p>
18
18
 
19
19
  <p align="center">
20
- <img src="https://img.shields.io/badge/version-0.1.1-ff69b4" alt="version" />
20
+ <img src="https://img.shields.io/badge/version-0.2.0-ff69b4" alt="version" />
21
21
  <img src="https://img.shields.io/badge/TypeScript-ready-3178c6?logo=typescript&logoColor=white" alt="typescript" />
22
22
  <img src="https://img.shields.io/badge/React-%E2%89%A5%2017-61dafb?logo=react&logoColor=white" alt="react" />
23
23
  <img src="https://img.shields.io/badge/runtime%20deps-0-22c55e" alt="zero deps" />
@@ -72,8 +72,10 @@ runtime dependencies.
72
72
 
73
73
  | Import path | Use it for |
74
74
  | ----------- | ---------- |
75
- | `pictoguys` | React projects that want `<Picto />` plus the core helpers |
76
- | `pictoguys/react` | Only the React component and its props |
75
+ | `pictoguys` | React projects that want `<Picto />`, `<PictoField />`, plus the core helpers |
76
+ | `pictoguys/react` | Only the single-picto React component `<Picto />` and its props |
77
+ | `pictoguys/react-canvas` | Only `<PictoField />` (the canvas batch renderer) and its props |
78
+ | `pictoguys/canvas` | The framework-agnostic batch renderer core, no React |
77
79
  | `pictoguys/core` | SVG strings, characters, presets, and catalog helpers without React |
78
80
  | `pictoguys/rng` | The tiny deterministic RNG only |
79
81
 
@@ -296,6 +298,168 @@ guy.stop() // freeze
296
298
  > on screen. If that picto is not currently rendered, the call simply does
297
299
  > nothing (no crash, no error, just a no-op). So render it first, then animate it.
298
300
 
301
+ ## Rendering many pictos
302
+
303
+ One picto? Reach for `<Picto>`. A whole wall of them (a leaderboard, a member
304
+ directory, a sticker sheet, hundreds or thousands of avatars)? That is where
305
+ `<PictoField>` comes in.
306
+
307
+ `<PictoField>` draws **many pictos onto a single `<canvas>`**, and it can keep
308
+ hundreds to thousands of them moving at 60fps. It is the **recommended way to
309
+ render multiple pictos**. `<Picto>` is not going anywhere and is not deprecated;
310
+ it is simply the right tool for one or a few pictos (or when you specifically
311
+ want real DOM nodes). Both draw the *same* art and play the *same* animations, so
312
+ you can mix them freely.
313
+
314
+ Hand it an array of characters and it lays them out for you:
315
+
316
+ ```tsx
317
+ import { picto, PictoField } from 'pictoguys'
318
+
319
+ // build as many little guys as you like
320
+ const chars = React.useMemo(
321
+ () => Array.from({ length: 500 }, (_, i) => picto.character(i)),
322
+ [],
323
+ )
324
+
325
+ export default function Wall() {
326
+ return <PictoField chars={chars} size={64} height="70vh" />
327
+ }
328
+ ```
329
+
330
+ That is a 500-picto grid that scrolls smoothly. By default `<PictoField>` owns
331
+ its own scroll viewport (an `overflow:auto` box sized by `height`, default
332
+ `'70vh'`) and only ever draws the pictos you can actually see, so the count
333
+ barely matters.
334
+
335
+ **Lay them out your way.** By default they auto-flow into a grid. Pass `cols` to
336
+ fix the column count, or pass an explicit array of top-left positions:
337
+
338
+ ```tsx
339
+ <PictoField chars={chars} cols={10} gap={16} />
340
+ <PictoField chars={chars} layout={[{ x: 0, y: 0 }, { x: 80, y: 0 }, /* ... */]} />
341
+ ```
342
+
343
+ **Animate the whole field at once.** The easy way is the declarative `animate`
344
+ prop, which plays one animation on *every* picto:
345
+
346
+ ```tsx
347
+ <PictoField chars={chars} animate="breath" /> {/* the whole crowd breathes */}
348
+ ```
349
+
350
+ The hands-on way is the imperative handle. `<PictoField>` forwards a ref to a
351
+ renderer you can poke directly. The animation target is either a single
352
+ `Character` from your `chars` array or the literal `'all'`:
353
+
354
+ ```tsx
355
+ import { picto, PictoField } from 'pictoguys'
356
+ import type { PictoRenderer } from 'pictoguys'
357
+
358
+ function Crowd() {
359
+ const ref = React.useRef<PictoRenderer>(null)
360
+ const chars = React.useMemo(
361
+ () => Array.from({ length: 300 }, (_, i) => picto.character(i)),
362
+ [],
363
+ )
364
+
365
+ return (
366
+ <>
367
+ <PictoField ref={ref} chars={chars} size={64} />
368
+ <button onClick={() => ref.current?.blink('all')}>everyone blink</button>
369
+ <button onClick={() => ref.current?.dance(chars[0])}>just the first one dances</button>
370
+ <button onClick={() => ref.current?.stop('all')}>chill</button>
371
+ </>
372
+ )
373
+ }
374
+ ```
375
+
376
+ The handle exposes `blink`, `jump`, `breath`, `dance`, `sleep`, `stop`, and a
377
+ general `play(target, name)` (pass `name: null` to stop), plus `start()`,
378
+ `dispose()`, and a `metrics()` peek. (Note: the looping animation string is
379
+ `'sleeping'`, but the method is `sleep()`.)
380
+
381
+ ### Flat or fancy: the `variant` prop
382
+
383
+ Both `<Picto>` and `<PictoField>` take a `variant` prop:
384
+
385
+ | Variant | Look |
386
+ | --------- | -------------------------------------------------------------- |
387
+ | `'fancy'` | The original look: gradient body plus soft shadows. **Default.** |
388
+ | `'flat'` | Drops the body gradient and the body shadow (the eye shadows stay). Cheaper to paint, which is handy across a big grid. |
389
+
390
+ ```tsx
391
+ <Picto seed="Bloop" variant="flat" />
392
+ <PictoField chars={chars} variant="flat" /> {/* lighter paint for huge fields */}
393
+ ```
394
+
395
+ `fancy` is the default everywhere, so leave it off if you want the classic look.
396
+
397
+ ### Best practices for many pictos
398
+
399
+ - **100+ pictos? Use `<PictoField>` (canvas), not a pile of `<Picto>`s.** One
400
+ canvas with culling beats hundreds of DOM nodes.
401
+ - **Reach for `variant="flat"` on very large grids.** It skips the gradient and
402
+ shadow, so each tile is cheaper to paint.
403
+ - **Reuse seeds.** Identical characters share one cached sprite, so a grid full of
404
+ repeats is nearly free to draw.
405
+ - **Let `<PictoField>` own its scroller** via the `height` prop (default `'70vh'`).
406
+ Only pass `scrollParentRef` when the field must scroll inside an existing scroll
407
+ container you already control.
408
+ - **Animate via the ref (`'all'` or a single character) or the `animate` prop.**
409
+ Both routes share the renderer's per-character clock.
410
+ - **The canvas pixels match the SVG** at the rendered size, so a `<PictoField>`
411
+ tile and a `<Picto>` of the same `size` look identical.
412
+ - **Keep `<Picto>` (DOM/SVG) for single avatars** or anywhere you need real DOM
413
+ nodes, CSS styling, or accessibility hooks on the element itself.
414
+
415
+ ### Without React (custom layouts, other frameworks)
416
+
417
+ The batch renderer has a framework-agnostic core under `pictoguys/canvas`. Give
418
+ `createPictoRenderer` a `<canvas>` and drive it yourself:
419
+
420
+ ```ts
421
+ import { createPictoRenderer, canvasSupported } from 'pictoguys/canvas'
422
+ import { picto } from 'pictoguys/core'
423
+
424
+ if (canvasSupported) {
425
+ const canvas = document.querySelector('canvas')!
426
+ const renderer = createPictoRenderer({ canvas, size: 64, variant: 'flat' })
427
+
428
+ renderer.setItems([
429
+ { char: picto.character('Bloop'), x: 0, y: 0 },
430
+ { char: picto.character('Mochi'), x: 80, y: 0 },
431
+ ])
432
+ renderer.start()
433
+ renderer.breath('all')
434
+ // ...later: renderer.dispose()
435
+ }
436
+ ```
437
+
438
+ `createPictoRenderer` is always safe to call: on the server or anywhere without a
439
+ canvas it returns a harmless no-op, and `canvasSupported` lets you fall back to
440
+ `<Picto>` when you need to.
441
+
442
+ ## PictoField props
443
+
444
+ `<PictoField>` accepts these. Only `chars` is required.
445
+
446
+ | Prop | Type | Default | What it does |
447
+ | ----------------- | ----------------------------- | -------- | ------------------------------------------------------- |
448
+ | `chars` | `Character[]` | — | The pictos to draw, one tile each, in order. Required. |
449
+ | `size` | `number` | `64` | Tile width and height, in pixels. |
450
+ | `variant` | `'fancy' \| 'flat'` | `'fancy'`| Body look (see above). |
451
+ | `background` | `boolean` | `false` | Draw a background tile behind each picto. |
452
+ | `layout` | `'grid' \| {x,y}[]` | `'grid'` | Auto-flow grid, or explicit top-left positions. |
453
+ | `cols` | `number` | auto | Grid columns. Omit to derive from the canvas width. |
454
+ | `gap` | `number` | `12` | Gap between grid tiles, in pixels. |
455
+ | `animate` | `"blink" \| "jump" \| "breath" \| "dance" \| "sleeping" \| null` | `null` | Play one animation on every picto. |
456
+ | `height` | `number \| string` | `'70vh'` | Height of the self-owned scroll viewport. |
457
+ | `scrollParentRef` | `RefObject<HTMLElement>` | none | Advanced: scroll inside your own container instead. |
458
+ | `dpr` | `number` | auto | Device-pixel-ratio override. |
459
+ | `maxCacheBytes` | `number` | 256 MB | Soft sprite-cache size cap. |
460
+ | `style` | `CSSProperties` | none | Applied to the inner `<canvas>`. |
461
+ | `className` | `string` | none | Applied to the outer wrapper `<div>`. |
462
+
299
463
  ## All the props
300
464
 
301
465
  `<Picto>` accepts these. Everything is optional.
@@ -308,10 +472,14 @@ guy.stop() // freeze
308
472
  | `size` | `number` | `120` | Width and height, in pixels. |
309
473
  | `background` | `boolean` | `false` | Set `true` to add a background tile. |
310
474
  | `animate` | `"blink" \| "jump" \| "breath" \| "dance" \| "sleeping"` | none | Play an animation on loop or once. |
475
+ | `variant` | `"fancy" \| "flat"` | `"fancy"`| Body look: `flat` drops the gradient/shadow. |
311
476
 
312
477
  Any normal `<span>` prop works too (`className`, `style`, `onClick`, and so on),
313
478
  because that is what `<Picto>` renders into.
314
479
 
480
+ > Rendering a crowd of pictos? See [Rendering many pictos](#rendering-many-pictos)
481
+ > for `<PictoField>`, the canvas batch renderer.
482
+
315
483
  Pictos are see-through by default, so they sit nicely on top of anything. Want a
316
484
  colored tile behind one instead? Flip one switch:
317
485