@fjandin/react-shader 0.0.2 → 0.0.4

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 ADDED
@@ -0,0 +1,147 @@
1
+ # @fjandin/react-shader
2
+
3
+ A React component for rendering WebGL fragment and vertex shaders. Designed to work with Shadertoy-style shaders with automatic uniform handling.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @fjandin/react-shader
9
+ # or
10
+ yarn add @fjandin/react-shader
11
+ # or
12
+ bun add @fjandin/react-shader
13
+ ```
14
+
15
+ ## Basic Usage
16
+
17
+ ```tsx
18
+ import { ReactShader } from "@fjandin/react-shader"
19
+
20
+ const fragment = `#version 300 es
21
+ precision highp float;
22
+
23
+ uniform float iTime;
24
+ uniform vec2 iResolution;
25
+
26
+ out vec4 fragColor;
27
+
28
+ void main() {
29
+ vec2 uv = gl_FragCoord.xy / iResolution;
30
+ vec3 color = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0, 2, 4));
31
+ fragColor = vec4(color, 1.0);
32
+ }
33
+ `
34
+
35
+ function App() {
36
+ return (
37
+ <div style={{ width: "800px", height: "600px" }}>
38
+ <ReactShader fragment={fragment} />
39
+ </div>
40
+ )
41
+ }
42
+ ```
43
+
44
+ ## Props
45
+
46
+ | Prop | Type | Required | Default | Description |
47
+ |------|------|----------|---------|-------------|
48
+ | `fragment` | `string` | Yes | - | GLSL fragment shader source code |
49
+ | `vertex` | `string` | No | Default quad shader | GLSL vertex shader source code |
50
+ | `uniforms` | `Record<string, UniformValue>` | No | `{}` | Custom uniform values |
51
+ | `className` | `string` | No | - | CSS class name for the container |
52
+ | `debug` | `boolean` | No | `false` | Show debug overlay with resolution and mouse info |
53
+ | `fullscreen` | `boolean` | No | `false` | Render as fixed fullscreen overlay |
54
+ | `timeScale` | `number` | No | `1` | Scale factor for elapsed time |
55
+ | `onFrame` | `(info: FrameInfo) => void` | No | - | Callback invoked on each frame |
56
+
57
+ ## Built-in Uniforms
58
+
59
+ These uniforms are automatically provided to your shader every frame:
60
+
61
+ | Uniform | GLSL Type | Description |
62
+ |---------|-----------|-------------|
63
+ | `iTime` | `float` | Elapsed time in seconds (scaled by `timeScale` prop) |
64
+ | `iMouse` | `vec2` | Mouse position in pixels (Y=0 at bottom) |
65
+ | `iMouseLeftDown` | `float` | `1.0` when left mouse button is pressed, `0.0` otherwise |
66
+ | `iResolution` | `vec2` | Canvas resolution in pixels (includes high-DPI scaling) |
67
+
68
+ ## Custom Uniforms
69
+
70
+ Pass custom uniform values via the `uniforms` prop:
71
+
72
+ ```tsx
73
+ <ReactShader
74
+ fragment={fragment}
75
+ uniforms={{
76
+ scale: 2.0, // float
77
+ offset: [0.5, 0.5], // vec2
78
+ color: [1.0, 0.5, 0.2], // vec3
79
+ transform: [1, 0, 0, 1], // vec4
80
+ }}
81
+ />
82
+ ```
83
+
84
+ Supported types:
85
+ - `number` → `float`
86
+ - `[number, number]` → `vec2`
87
+ - `[number, number, number]` → `vec3`
88
+ - `[number, number, number, number]` → `vec4`
89
+
90
+ ## Frame Callback
91
+
92
+ Use the `onFrame` callback to update uniforms based on animation timing:
93
+
94
+ ```tsx
95
+ function App() {
96
+ const [customTime, setCustomTime] = useState(0)
97
+
98
+ return (
99
+ <ReactShader
100
+ fragment={fragment}
101
+ uniforms={{ customTime }}
102
+ onFrame={(info) => {
103
+ setCustomTime((prev) => prev + info.deltaTime * 0.5)
104
+ }}
105
+ />
106
+ )
107
+ }
108
+ ```
109
+
110
+ The `FrameInfo` object contains:
111
+ - `deltaTime` - Time since last frame in seconds
112
+ - `time` - Total elapsed time in seconds
113
+ - `resolution` - Canvas resolution as `[width, height]`
114
+ - `mouse` - Mouse position as `[x, y]`
115
+
116
+ ## TypeScript
117
+
118
+ All types are exported:
119
+
120
+ ```tsx
121
+ import type {
122
+ Vec2,
123
+ Vec3,
124
+ Vec4,
125
+ UniformValue,
126
+ DefaultUniforms,
127
+ FrameInfo,
128
+ ReactShaderProps,
129
+ } from "@fjandin/react-shader"
130
+ ```
131
+
132
+ ## Features
133
+
134
+ - WebGL2 with WebGL1 fallback
135
+ - High-DPI display support via `devicePixelRatio`
136
+ - Automatic canvas resizing
137
+ - Shader compilation error display
138
+ - Context loss/restoration handling
139
+ - Mouse tracking with WebGL coordinate convention
140
+
141
+ ## Requirements
142
+
143
+ - React >= 17.0.0
144
+
145
+ ## License
146
+
147
+ MIT
@@ -1,3 +1,3 @@
1
1
  import type { ReactShaderProps } from "./types";
2
- export declare function ReactShader({ className, fragment, vertex, uniforms, debug, fullscreen, timeScale, onFrame, }: ReactShaderProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ReactShader({ className, fragment, vertex, uniforms, fullscreen, timeScale, onFrame, }: ReactShaderProps): import("react/jsx-runtime").JSX.Element;
3
3
  //# sourceMappingURL=ReactShader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactShader.d.ts","sourceRoot":"","sources":["../src/ReactShader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,SAAS,CAAA;AAerD,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,QAAQ,EACR,MAAuB,EACvB,QAAQ,EACR,KAAa,EACb,UAAkB,EAClB,SAAa,EACb,OAAO,GACR,EAAE,gBAAgB,2CAiHlB"}
1
+ {"version":3,"file":"ReactShader.d.ts","sourceRoot":"","sources":["../src/ReactShader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAU/C,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,QAAQ,EACR,MAAuB,EACvB,QAAQ,EACR,UAAkB,EAClB,SAAa,EACb,OAAO,GACR,EAAE,gBAAgB,2CAwElB"}
@@ -1 +1 @@
1
- {"version":3,"file":"frontend.d.ts","sourceRoot":"","sources":["../../src/example/frontend.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,aAAa,CAAA;AAsBpB,wBAAgB,GAAG,4CAyBlB"}
1
+ {"version":3,"file":"frontend.d.ts","sourceRoot":"","sources":["../../src/example/frontend.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,aAAa,CAAA;AAsBpB,wBAAgB,GAAG,4CA6BlB"}
package/dist/index.cjs CHANGED
@@ -329,17 +329,11 @@ function ReactShader({
329
329
  fragment,
330
330
  vertex = DEFAULT_VERTEX,
331
331
  uniforms,
332
- debug = false,
333
332
  fullscreen = false,
334
333
  timeScale = 1,
335
334
  onFrame
336
335
  }) {
337
336
  const [error, setError] = import_react2.useState(null);
338
- const [debugInfo, setDebugInfo] = import_react2.useState({
339
- iResolution: [0, 0],
340
- iMouse: [0, 0]
341
- });
342
- const lastDebugUpdateRef = import_react2.useRef(0);
343
337
  const handleError = import_react2.useCallback((err) => {
344
338
  setError(err.message);
345
339
  console.error("ReactShader error:", err);
@@ -348,17 +342,7 @@ function ReactShader({
348
342
  if (onFrame) {
349
343
  onFrame(info);
350
344
  }
351
- if (!debug)
352
- return;
353
- const now = performance.now();
354
- if (now - lastDebugUpdateRef.current < 100)
355
- return;
356
- lastDebugUpdateRef.current = now;
357
- setDebugInfo({
358
- iResolution: info.resolution,
359
- iMouse: info.mouse
360
- });
361
- }, [debug, onFrame]);
345
+ }, [onFrame]);
362
346
  import_react2.useEffect(() => {
363
347
  setError(null);
364
348
  }, [fragment, vertex]);
@@ -396,7 +380,9 @@ function ReactShader({
396
380
  fontSize: "12px",
397
381
  padding: "16px",
398
382
  overflow: "auto",
399
- boxSizing: "border-box"
383
+ boxSizing: "border-box",
384
+ width: "100%",
385
+ height: "100%"
400
386
  },
401
387
  children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV("pre", {
402
388
  style: { margin: 0, whiteSpace: "pre-wrap" },
@@ -404,48 +390,9 @@ function ReactShader({
404
390
  }, undefined, false, undefined, this)
405
391
  }, undefined, false, undefined, this);
406
392
  }
407
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
408
- style: containerStyle,
409
- children: [
410
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV("canvas", {
411
- ref: canvasRef,
412
- className,
413
- style: { display: "block", width: "100%", height: "100%" }
414
- }, undefined, false, undefined, this),
415
- debug && /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
416
- style: {
417
- position: "absolute",
418
- top: 8,
419
- left: 8,
420
- backgroundColor: "rgba(0, 0, 0, 0.7)",
421
- color: "#fff",
422
- fontFamily: "monospace",
423
- fontSize: "12px",
424
- padding: "8px",
425
- borderRadius: "4px",
426
- pointerEvents: "none"
427
- },
428
- children: [
429
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
430
- children: [
431
- "iResolution: [",
432
- debugInfo.iResolution[0],
433
- ", ",
434
- debugInfo.iResolution[1],
435
- "]"
436
- ]
437
- }, undefined, true, undefined, this),
438
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
439
- children: [
440
- "iMouse: [",
441
- Math.round(debugInfo.iMouse[0]),
442
- ", ",
443
- Math.round(debugInfo.iMouse[1]),
444
- "]"
445
- ]
446
- }, undefined, true, undefined, this)
447
- ]
448
- }, undefined, true, undefined, this)
449
- ]
450
- }, undefined, true, undefined, this);
393
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("canvas", {
394
+ ref: canvasRef,
395
+ className,
396
+ style: { display: "block", width: "100%", height: "100%" }
397
+ }, undefined, false, undefined, this);
451
398
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/ReactShader.tsx
2
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState } from "react";
2
+ import { useCallback as useCallback2, useEffect as useEffect2, useState } from "react";
3
3
 
4
4
  // src/hooks/useWebGL.ts
5
5
  import { useCallback, useEffect, useRef } from "react";
@@ -294,17 +294,11 @@ function ReactShader({
294
294
  fragment,
295
295
  vertex = DEFAULT_VERTEX,
296
296
  uniforms,
297
- debug = false,
298
297
  fullscreen = false,
299
298
  timeScale = 1,
300
299
  onFrame
301
300
  }) {
302
301
  const [error, setError] = useState(null);
303
- const [debugInfo, setDebugInfo] = useState({
304
- iResolution: [0, 0],
305
- iMouse: [0, 0]
306
- });
307
- const lastDebugUpdateRef = useRef2(0);
308
302
  const handleError = useCallback2((err) => {
309
303
  setError(err.message);
310
304
  console.error("ReactShader error:", err);
@@ -313,17 +307,7 @@ function ReactShader({
313
307
  if (onFrame) {
314
308
  onFrame(info);
315
309
  }
316
- if (!debug)
317
- return;
318
- const now = performance.now();
319
- if (now - lastDebugUpdateRef.current < 100)
320
- return;
321
- lastDebugUpdateRef.current = now;
322
- setDebugInfo({
323
- iResolution: info.resolution,
324
- iMouse: info.mouse
325
- });
326
- }, [debug, onFrame]);
310
+ }, [onFrame]);
327
311
  useEffect2(() => {
328
312
  setError(null);
329
313
  }, [fragment, vertex]);
@@ -361,7 +345,9 @@ function ReactShader({
361
345
  fontSize: "12px",
362
346
  padding: "16px",
363
347
  overflow: "auto",
364
- boxSizing: "border-box"
348
+ boxSizing: "border-box",
349
+ width: "100%",
350
+ height: "100%"
365
351
  },
366
352
  children: /* @__PURE__ */ jsxDEV("pre", {
367
353
  style: { margin: 0, whiteSpace: "pre-wrap" },
@@ -369,50 +355,11 @@ function ReactShader({
369
355
  }, undefined, false, undefined, this)
370
356
  }, undefined, false, undefined, this);
371
357
  }
372
- return /* @__PURE__ */ jsxDEV("div", {
373
- style: containerStyle,
374
- children: [
375
- /* @__PURE__ */ jsxDEV("canvas", {
376
- ref: canvasRef,
377
- className,
378
- style: { display: "block", width: "100%", height: "100%" }
379
- }, undefined, false, undefined, this),
380
- debug && /* @__PURE__ */ jsxDEV("div", {
381
- style: {
382
- position: "absolute",
383
- top: 8,
384
- left: 8,
385
- backgroundColor: "rgba(0, 0, 0, 0.7)",
386
- color: "#fff",
387
- fontFamily: "monospace",
388
- fontSize: "12px",
389
- padding: "8px",
390
- borderRadius: "4px",
391
- pointerEvents: "none"
392
- },
393
- children: [
394
- /* @__PURE__ */ jsxDEV("div", {
395
- children: [
396
- "iResolution: [",
397
- debugInfo.iResolution[0],
398
- ", ",
399
- debugInfo.iResolution[1],
400
- "]"
401
- ]
402
- }, undefined, true, undefined, this),
403
- /* @__PURE__ */ jsxDEV("div", {
404
- children: [
405
- "iMouse: [",
406
- Math.round(debugInfo.iMouse[0]),
407
- ", ",
408
- Math.round(debugInfo.iMouse[1]),
409
- "]"
410
- ]
411
- }, undefined, true, undefined, this)
412
- ]
413
- }, undefined, true, undefined, this)
414
- ]
415
- }, undefined, true, undefined, this);
358
+ return /* @__PURE__ */ jsxDEV("canvas", {
359
+ ref: canvasRef,
360
+ className,
361
+ style: { display: "block", width: "100%", height: "100%" }
362
+ }, undefined, false, undefined, this);
416
363
  }
417
364
  export {
418
365
  ReactShader
package/dist/types.d.ts CHANGED
@@ -8,7 +8,6 @@ export interface ReactShaderProps {
8
8
  fragment: string;
9
9
  vertex?: string;
10
10
  uniforms?: Record<string, UniformValue>;
11
- debug?: boolean;
12
11
  fullscreen?: boolean;
13
12
  timeScale?: number;
14
13
  onFrame?: (info: FrameInfo) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACnC,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAC3C,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEnD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEtD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,IAAI,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,IAAI,CAAA;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACnC,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAC3C,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEnD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEtD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,IAAI,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,IAAI,CAAA;CAClB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjandin/react-shader",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "React component for rendering WebGL shaders",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",