@instantshader/react 0.5.0 → 0.6.1

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
@@ -4,8 +4,9 @@
4
4
 
5
5
  React bindings for [instantshader](https://www.npmjs.com/package/instantshader), a
6
6
  zero-dependency animated WebGL gradient engine. Drop a `<Flow>`, `<Beam>`, `<Bloom>`, `<Halo>`, `<Strata>`, `<Dune>` or
7
- `<Whorl>` component into any sized wrapper to mount a live, animated gradient. Built by
8
- [InstantGradient](https://instantgradient.com/app).
7
+ `<Whorl>` component into any sized wrapper to mount a live, animated gradient, and
8
+ add dither, pixelate, halftone or ASCII effects over it or over your own image.
9
+ Built by [InstantGradient](https://instantgradient.com/app).
9
10
 
10
11
  ## Install
11
12
 
@@ -56,7 +57,43 @@ import { Bloom } from "@instantshader/react";
56
57
 
57
58
  See the [`instantshader` README](https://www.npmjs.com/package/instantshader)
58
59
  for what each param does and for the shader defs themselves (re-exported from
59
- here as `flow`, `beam` and `bloom`).
60
+ here as `flow`, `beam`, `bloom`, `halo`, `strata`, `dune` and `whorl`).
61
+
62
+ ## Effects
63
+
64
+ Every shader component takes an `effects` prop: a list of layers, bottom
65
+ first, each an effect def with its params.
66
+
67
+ ```tsx
68
+ import { Bloom, dither } from "@instantshader/react";
69
+
70
+ <Bloom
71
+ colors={["#140f30", "#9c2168", "#eb6a4e", "#fcd87c"]}
72
+ effects={[{ effect: dither, params: { pattern: "blueNoise", colorMode: "palette", levels: 4 } }]}
73
+ style={{ width: "100%", height: "100%" }}
74
+ />;
75
+ ```
76
+
77
+ `<ShaderStack>` takes a `source` instead of a fixed shader, which is how you
78
+ put effects over an image. Any `<img>`, `<canvas>` or `<video>` element works
79
+ as `media`:
80
+
81
+ ```tsx
82
+ import { ShaderStack, halftone } from "@instantshader/react";
83
+
84
+ <ShaderStack
85
+ source={{ kind: "media", media: img, fit: "cover" }}
86
+ colors={["#111111", "#f4f1ea"]}
87
+ effects={[{ effect: halftone, params: { size: 20, angle: 30 } }]}
88
+ style={{ width: "100%", height: "100%" }}
89
+ />;
90
+ ```
91
+
92
+ Changing `effects` or their params updates the canvas in place. A new
93
+ `source`, `seed`, `background` or `fontFamily` remounts it. The four effect
94
+ defs (`pixelate`, `dither`, `halftone`, `ascii`) are re-exported from here,
95
+ and the [repository README](https://github.com/ugolbck/instantshader#effects)
96
+ lists every param with its range.
60
97
 
61
98
  ## Seamless loops
62
99
 
@@ -67,7 +104,7 @@ at the wrap:
67
104
  <Flow colors={colors} loopSeconds={30} style={{ width: "100%", height: "100%" }} />
68
105
  ```
69
106
 
70
- 15–60s is the comfortable range, and the period is measured in animation
71
- seconds (so it interacts with `speed`). See the
107
+ 15 to 60s is the comfortable range, and the period is measured in animation
108
+ seconds, so it interacts with `speed`. See the
72
109
  [`instantshader` README](https://www.npmjs.com/package/instantshader) for the
73
110
  full details.
package/dist/index.d.ts CHANGED
@@ -1,53 +1,79 @@
1
1
  import { CSSProperties, ReactElement } from "react";
2
- import { ParamDef, ShaderDef, ShaderDef as ShaderDef$1, beam, bloom, dune, flow, halo, strata, whorl } from "instantshader";
2
+ import { EffectDef, EffectLayer, EffectLayer as EffectLayer$1, EffectParamDef, ParamDef, ParamValue, ShaderDef, ShaderDef as ShaderDef$1, Source, Source as Source$1, ascii, beam, bloom, dither, dune, flow, halftone, halo, pixelate, strata, whorl } from "instantshader";
3
3
 
4
- //#region src/ShaderCanvas.d.ts
5
- interface ShaderCanvasProps {
6
- shader: ShaderDef$1;
4
+ //#region src/ShaderStack.d.ts
5
+ interface ShaderStackProps {
6
+ source: Source$1;
7
+ /** Effect layers, bottom to top. A new array with the same content does
8
+ * not recompile anything. */
9
+ effects?: EffectLayer$1[];
10
+ /** Palette. Generators draw with it; effects in "palette" color mode map
11
+ * tone through it. */
7
12
  colors: string[];
8
13
  /**
9
- * Merged into the shader's current params (removed/omitted keys are NOT
10
- * reset to their default — they keep whatever value was last applied).
11
- * Setting this prop back to `undefined` does not revert to defaults
12
- * either: the underlying MountHandle is only updated when `params` is
13
- * truthy, so the last applied values simply stick.
14
- */
15
- params?: Record<string, number>;
16
- /**
17
14
  * Setting this prop back to `undefined` does NOT revert playback to the
18
- * default speed — the underlying MountHandle is only updated when `speed`
19
- * is not `undefined`, so whatever speed was last applied keeps playing.
15
+ * default speed — the underlying handle is only updated when `speed` is
16
+ * not `undefined`, so whatever speed was last applied keeps playing.
20
17
  */
21
18
  speed?: number;
22
19
  seed?: number;
23
- /**
24
- * Makes the animation repeat seamlessly every `loopSeconds` animation
25
- * seconds. Unlike `params` and `speed`, setting this back to `undefined`
26
- * DOES take effect — it turns looping off — because "no loop" is a real
27
- * state the underlying handle can be put into.
28
- */
20
+ /** Makes the animation repeat seamlessly every `loopSeconds` animation
21
+ * seconds. Setting it back to `undefined` turns looping off. */
29
22
  loopSeconds?: number;
30
23
  paused?: boolean;
24
+ /** Shown behind contain-fit media and under media alpha. Mount-time only. */
25
+ background?: string;
26
+ /** CSS font-family for effects that draw text (ASCII). Load it first with
27
+ * `document.fonts.load`. Mount-time only. */
28
+ fontFamily?: string;
31
29
  className?: string;
32
30
  style?: CSSProperties;
33
31
  }
34
32
  /**
35
- * Mounts a live InstantShader gradient into a wrapper `<div>`. The canvas
36
- * fills that div at 100%/100% (mountGradient styles it that way), so the
37
- * consumer MUST give the wrapper an explicit size (via `style`, `className`,
38
- * or a sized parent) — this component does not impose one beyond
39
- * `position: relative`.
33
+ * Mounts a live stack into a wrapper `<div>`. The canvas fills that div at
34
+ * 100%/100%, so the consumer MUST give the wrapper an explicit size (via
35
+ * `style`, `className`, or a sized parent) — this component does not impose
36
+ * one beyond `position: relative`.
40
37
  */
41
- declare function ShaderCanvas({
42
- shader,
38
+ declare function ShaderStack({
39
+ source,
40
+ effects,
43
41
  colors,
44
- params,
45
42
  speed,
46
43
  seed,
47
44
  loopSeconds,
48
45
  paused,
46
+ background,
47
+ fontFamily,
49
48
  className,
50
49
  style
50
+ }: ShaderStackProps): ReactElement;
51
+ //#endregion
52
+ //#region src/ShaderCanvas.d.ts
53
+ interface ShaderCanvasProps extends Omit<ShaderStackProps, "source" | "effects" | "background"> {
54
+ shader: ShaderDef$1;
55
+ /**
56
+ * Merged into the shader's current params (removed/omitted keys are NOT
57
+ * reset to their default — they keep whatever value was last applied).
58
+ * Setting this prop back to `undefined` does not revert to defaults
59
+ * either: the underlying handle is only updated when `params` is truthy,
60
+ * so the last applied values simply stick.
61
+ */
62
+ params?: Record<string, number>;
63
+ /** Effect layers drawn over the gradient, bottom to top:
64
+ * `[{ effect: dither, params: { size: 4, colorMode: "palette" } }]`. */
65
+ effects?: EffectLayer$1[];
66
+ }
67
+ /**
68
+ * Mounts a live InstantShader gradient, optionally with effects over it. See
69
+ * ShaderStack for sizing: the consumer MUST give the wrapper an explicit size.
70
+ * Remounts only when `shader` or `seed` changes.
71
+ */
72
+ declare function ShaderCanvas({
73
+ shader,
74
+ params,
75
+ effects,
76
+ ...rest
51
77
  }: ShaderCanvasProps): ReactElement;
52
78
  //#endregion
53
79
  //#region src/shaders.d.ts
@@ -59,4 +85,4 @@ declare function Strata(props: Omit<ShaderCanvasProps, "shader">): ReactElement;
59
85
  declare function Dune(props: Omit<ShaderCanvasProps, "shader">): ReactElement;
60
86
  declare function Whorl(props: Omit<ShaderCanvasProps, "shader">): ReactElement;
61
87
  //#endregion
62
- export { Beam, Bloom, Dune, Flow, Halo, type ParamDef, ShaderCanvas, type ShaderCanvasProps, type ShaderDef, Strata, Whorl, beam, bloom, dune, flow, halo, strata, whorl };
88
+ export { Beam, Bloom, Dune, type EffectDef, type EffectLayer, type EffectParamDef, Flow, Halo, type ParamDef, type ParamValue, ShaderCanvas, type ShaderCanvasProps, type ShaderDef, ShaderStack, type ShaderStackProps, type Source, Strata, Whorl, ascii, beam, bloom, dither, dune, flow, halftone, halo, pixelate, strata, whorl };
package/dist/index.js CHANGED
@@ -1,30 +1,38 @@
1
1
  "use client";
2
2
  import { useEffect, useMemo, useRef } from "react";
3
- import { beam, beam as beam$1, bloom, bloom as bloom$1, dune, dune as dune$1, flow, flow as flow$1, halo, halo as halo$1, mountGradient, strata, strata as strata$1, whorl, whorl as whorl$1 } from "instantshader";
3
+ import { ascii, beam, beam as beam$1, bloom, bloom as bloom$1, dither, dune, dune as dune$1, flow, flow as flow$1, halftone, halo, halo as halo$1, mountStack, pixelate, strata, strata as strata$1, whorl, whorl as whorl$1 } from "instantshader";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
 
6
- //#region src/ShaderCanvas.tsx
6
+ //#region src/ShaderStack.tsx
7
+ function sortedSig(o) {
8
+ if (!o) return "";
9
+ return Object.keys(o).sort().map((k) => `${k}:${String(o[k])}`).join(",");
10
+ }
7
11
  /**
8
- * Mounts a live InstantShader gradient into a wrapper `<div>`. The canvas
9
- * fills that div at 100%/100% (mountGradient styles it that way), so the
10
- * consumer MUST give the wrapper an explicit size (via `style`, `className`,
11
- * or a sized parent) — this component does not impose one beyond
12
- * `position: relative`.
12
+ * Mounts a live stack into a wrapper `<div>`. The canvas fills that div at
13
+ * 100%/100%, so the consumer MUST give the wrapper an explicit size (via
14
+ * `style`, `className`, or a sized parent) — this component does not impose
15
+ * one beyond `position: relative`.
13
16
  */
14
- function ShaderCanvas({ shader, colors, params, speed, seed, loopSeconds, paused, className, style }) {
17
+ function ShaderStack({ source, effects, colors, speed, seed, loopSeconds, paused, background, fontFamily, className, style }) {
15
18
  const containerRef = useRef(null);
16
19
  const handleRef = useRef(null);
17
- const p = useMemo(() => params, [params ? Object.entries(params).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${k}:${v}`).join(",") : ""]);
20
+ const sourceIdentity = source.kind === "generator" ? source.shader : source.media;
21
+ const fit = source.kind === "media" ? source.fit : void 0;
22
+ const sourceParams = useMemo(() => source.kind === "generator" ? source.params : void 0, [source.kind === "generator" ? sortedSig(source.params) : ""]);
23
+ const layers = useMemo(() => effects ?? [], [(effects ?? []).map((l) => `${l.effect.id}|${l.enabled ?? true}|${sortedSig(l.params)}`).join(";")]);
18
24
  useEffect(() => {
19
25
  const container = containerRef.current;
20
26
  if (!container) return;
21
- const handle = mountGradient(container, {
22
- shader,
27
+ const handle = mountStack(container, {
28
+ source,
29
+ effects: layers,
23
30
  colors,
24
- params: p,
25
31
  speed,
26
32
  seed,
27
- loopSeconds
33
+ loopSeconds,
34
+ background,
35
+ fontFamily
28
36
  });
29
37
  handleRef.current = handle;
30
38
  if (paused) handle.pause();
@@ -32,13 +40,22 @@ function ShaderCanvas({ shader, colors, params, speed, seed, loopSeconds, paused
32
40
  handle.dispose();
33
41
  handleRef.current = null;
34
42
  };
35
- }, [shader, seed]);
43
+ }, [
44
+ sourceIdentity,
45
+ fit,
46
+ seed,
47
+ background,
48
+ fontFamily
49
+ ]);
36
50
  useEffect(() => {
37
51
  handleRef.current?.setColors(colors);
38
52
  }, [colors]);
39
53
  useEffect(() => {
40
- if (p) handleRef.current?.setParams(p);
41
- }, [p]);
54
+ if (sourceParams) handleRef.current?.setSourceParams(sourceParams);
55
+ }, [sourceParams]);
56
+ useEffect(() => {
57
+ handleRef.current?.setEffects(layers);
58
+ }, [layers]);
42
59
  useEffect(() => {
43
60
  if (speed !== void 0) handleRef.current?.setSpeed(speed);
44
61
  }, [speed]);
@@ -59,6 +76,25 @@ function ShaderCanvas({ shader, colors, params, speed, seed, loopSeconds, paused
59
76
  });
60
77
  }
61
78
 
79
+ //#endregion
80
+ //#region src/ShaderCanvas.tsx
81
+ /**
82
+ * Mounts a live InstantShader gradient, optionally with effects over it. See
83
+ * ShaderStack for sizing: the consumer MUST give the wrapper an explicit size.
84
+ * Remounts only when `shader` or `seed` changes.
85
+ */
86
+ function ShaderCanvas({ shader, params, effects,...rest }) {
87
+ return /* @__PURE__ */ jsx(ShaderStack, {
88
+ source: {
89
+ kind: "generator",
90
+ shader,
91
+ params
92
+ },
93
+ effects,
94
+ ...rest
95
+ });
96
+ }
97
+
62
98
  //#endregion
63
99
  //#region src/shaders.tsx
64
100
  function Flow(props) {
@@ -105,4 +141,4 @@ function Whorl(props) {
105
141
  }
106
142
 
107
143
  //#endregion
108
- export { Beam, Bloom, Dune, Flow, Halo, ShaderCanvas, Strata, Whorl, beam, bloom, dune, flow, halo, strata, whorl };
144
+ export { Beam, Bloom, Dune, Flow, Halo, ShaderCanvas, ShaderStack, Strata, Whorl, ascii, beam, bloom, dither, dune, flow, halftone, halo, pixelate, strata, whorl };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@instantshader/react",
3
- "version": "0.5.0",
4
- "description": "React bindings for InstantShader animated WebGL gradients.",
3
+ "version": "0.6.1",
4
+ "description": "React components for InstantShader: animated WebGL gradients and image effects.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "react"
37
37
  ],
38
38
  "dependencies": {
39
- "instantshader": "0.5.0"
39
+ "instantshader": "0.6.1"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "react": "^18 || ^19",