react-shadertoy 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wrennly
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # react-shadertoy
2
+
3
+ Run [Shadertoy](https://www.shadertoy.com/) GLSL shaders in React. Copy-paste and it works.
4
+
5
+ - Zero dependencies (just React)
6
+ - All Shadertoy uniforms supported (`iTime`, `iResolution`, `iMouse`, `iDate`, etc.)
7
+ - Mouse & touch interaction built-in
8
+ - TypeScript-first
9
+ - < 4KB gzipped
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install react-shadertoy
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import { Shadertoy } from 'react-shadertoy'
21
+
22
+ function App() {
23
+ return (
24
+ <Shadertoy
25
+ style={{ width: '100%', height: '400px' }}
26
+ fragmentShader={`
27
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
28
+ vec2 uv = fragCoord / iResolution.xy;
29
+ fragColor = vec4(uv, 0.5 + 0.5 * sin(iTime), 1.0);
30
+ }
31
+ `}
32
+ />
33
+ )
34
+ }
35
+ ```
36
+
37
+ Find a shader on [Shadertoy](https://www.shadertoy.com/), copy the GLSL code, paste it into `fragmentShader`. Done.
38
+
39
+ ## Hooks API
40
+
41
+ ```tsx
42
+ import { useShadertoy } from 'react-shadertoy'
43
+
44
+ function MyShader() {
45
+ const { canvasRef, isReady, error, pause, resume } = useShadertoy({
46
+ fragmentShader: `
47
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
48
+ vec2 uv = fragCoord / iResolution.xy;
49
+ fragColor = vec4(uv, 0.5 + 0.5 * sin(iTime), 1.0);
50
+ }
51
+ `,
52
+ })
53
+
54
+ return <canvas ref={canvasRef} style={{ width: '100%', height: '400px' }} />
55
+ }
56
+ ```
57
+
58
+ ## Props
59
+
60
+ | Prop | Type | Default | Description |
61
+ |------|------|---------|-------------|
62
+ | `fragmentShader` | `string` | required | Shadertoy GLSL code |
63
+ | `style` | `CSSProperties` | — | Container style |
64
+ | `className` | `string` | — | Container className |
65
+ | `paused` | `boolean` | `false` | Pause rendering |
66
+ | `speed` | `number` | `1.0` | `iTime` speed multiplier |
67
+ | `pixelRatio` | `number` | `devicePixelRatio` | Canvas pixel ratio |
68
+ | `mouse` | `boolean` | `true` | Enable mouse/touch tracking |
69
+ | `onError` | `(error: string) => void` | — | GLSL compile error callback |
70
+ | `onLoad` | `() => void` | — | WebGL ready callback |
71
+
72
+ ## Supported Uniforms
73
+
74
+ | Uniform | Type | Description |
75
+ |---------|------|-------------|
76
+ | `iResolution` | `vec3` | Viewport size in pixels |
77
+ | `iTime` | `float` | Elapsed time in seconds |
78
+ | `iTimeDelta` | `float` | Time since last frame |
79
+ | `iFrame` | `int` | Frame counter |
80
+ | `iMouse` | `vec4` | Mouse position & click state |
81
+ | `iDate` | `vec4` | Year, month, day, seconds |
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,45 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CSSProperties, RefObject } from 'react';
3
+
4
+ interface ShadertoyProps {
5
+ /** Shadertoy-compatible GLSL fragment shader (must contain mainImage) */
6
+ fragmentShader: string;
7
+ /** Container style */
8
+ style?: CSSProperties;
9
+ /** Container className */
10
+ className?: string;
11
+ /** Pause rendering (default: false) */
12
+ paused?: boolean;
13
+ /** iTime speed multiplier (default: 1.0) */
14
+ speed?: number;
15
+ /** Device pixel ratio (default: window.devicePixelRatio) */
16
+ pixelRatio?: number;
17
+ /** Enable mouse/touch tracking (default: true) */
18
+ mouse?: boolean;
19
+ /** Called when GLSL compilation fails */
20
+ onError?: (error: string) => void;
21
+ /** Called when WebGL is ready */
22
+ onLoad?: () => void;
23
+ }
24
+ interface UseShadertoyOptions {
25
+ fragmentShader: string;
26
+ paused?: boolean;
27
+ speed?: number;
28
+ pixelRatio?: number;
29
+ mouse?: boolean;
30
+ onError?: (error: string) => void;
31
+ onLoad?: () => void;
32
+ }
33
+ interface UseShadertoyReturn {
34
+ canvasRef: RefObject<HTMLCanvasElement | null>;
35
+ isReady: boolean;
36
+ error: string | null;
37
+ pause: () => void;
38
+ resume: () => void;
39
+ }
40
+
41
+ declare function Shadertoy({ fragmentShader, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
42
+
43
+ declare function useShadertoy({ fragmentShader, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn;
44
+
45
+ export { Shadertoy, type ShadertoyProps, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
@@ -0,0 +1,45 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CSSProperties, RefObject } from 'react';
3
+
4
+ interface ShadertoyProps {
5
+ /** Shadertoy-compatible GLSL fragment shader (must contain mainImage) */
6
+ fragmentShader: string;
7
+ /** Container style */
8
+ style?: CSSProperties;
9
+ /** Container className */
10
+ className?: string;
11
+ /** Pause rendering (default: false) */
12
+ paused?: boolean;
13
+ /** iTime speed multiplier (default: 1.0) */
14
+ speed?: number;
15
+ /** Device pixel ratio (default: window.devicePixelRatio) */
16
+ pixelRatio?: number;
17
+ /** Enable mouse/touch tracking (default: true) */
18
+ mouse?: boolean;
19
+ /** Called when GLSL compilation fails */
20
+ onError?: (error: string) => void;
21
+ /** Called when WebGL is ready */
22
+ onLoad?: () => void;
23
+ }
24
+ interface UseShadertoyOptions {
25
+ fragmentShader: string;
26
+ paused?: boolean;
27
+ speed?: number;
28
+ pixelRatio?: number;
29
+ mouse?: boolean;
30
+ onError?: (error: string) => void;
31
+ onLoad?: () => void;
32
+ }
33
+ interface UseShadertoyReturn {
34
+ canvasRef: RefObject<HTMLCanvasElement | null>;
35
+ isReady: boolean;
36
+ error: string | null;
37
+ pause: () => void;
38
+ resume: () => void;
39
+ }
40
+
41
+ declare function Shadertoy({ fragmentShader, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
42
+
43
+ declare function useShadertoy({ fragmentShader, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn;
44
+
45
+ export { Shadertoy, type ShadertoyProps, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
package/dist/index.js ADDED
@@ -0,0 +1,326 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/useShadertoy.ts
7
+
8
+ // src/renderer.ts
9
+ var QUAD_VERTICES = new Float32Array([
10
+ -1,
11
+ -1,
12
+ 1,
13
+ -1,
14
+ -1,
15
+ 1,
16
+ -1,
17
+ 1,
18
+ 1,
19
+ -1,
20
+ 1,
21
+ 1
22
+ ]);
23
+ var VERTEX_SHADER = `
24
+ attribute vec2 position;
25
+ void main() {
26
+ gl_Position = vec4(position, 0.0, 1.0);
27
+ }
28
+ `;
29
+ function wrapFragmentShader(shader) {
30
+ return `precision highp float;
31
+
32
+ uniform vec3 iResolution;
33
+ uniform float iTime;
34
+ uniform float iTimeDelta;
35
+ uniform int iFrame;
36
+ uniform vec4 iMouse;
37
+ uniform vec4 iDate;
38
+
39
+ ${shader}
40
+
41
+ void main() {
42
+ mainImage(gl_FragColor, gl_FragCoord.xy);
43
+ }
44
+ `;
45
+ }
46
+ function compileShader(gl, type, source) {
47
+ const shader = gl.createShader(type);
48
+ if (!shader) return "Failed to create shader";
49
+ gl.shaderSource(shader, source);
50
+ gl.compileShader(shader);
51
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
52
+ const log = gl.getShaderInfoLog(shader) || "Unknown compile error";
53
+ gl.deleteShader(shader);
54
+ return log;
55
+ }
56
+ return shader;
57
+ }
58
+ function createRenderer(canvas, fragmentShader) {
59
+ const gl = canvas.getContext("webgl", {
60
+ antialias: false,
61
+ alpha: true,
62
+ premultipliedAlpha: false
63
+ });
64
+ if (!gl) return "WebGL not supported";
65
+ const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
66
+ if (typeof vert === "string") return vert;
67
+ const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader));
68
+ if (typeof frag === "string") return frag;
69
+ const program = gl.createProgram();
70
+ if (!program) return "Failed to create program";
71
+ gl.attachShader(program, vert);
72
+ gl.attachShader(program, frag);
73
+ gl.linkProgram(program);
74
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
75
+ const log = gl.getProgramInfoLog(program) || "Unknown link error";
76
+ gl.deleteProgram(program);
77
+ return log;
78
+ }
79
+ gl.deleteShader(vert);
80
+ gl.deleteShader(frag);
81
+ const buffer = gl.createBuffer();
82
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
83
+ gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW);
84
+ const positionLoc = gl.getAttribLocation(program, "position");
85
+ gl.enableVertexAttribArray(positionLoc);
86
+ gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
87
+ const locations = {
88
+ iResolution: gl.getUniformLocation(program, "iResolution"),
89
+ iTime: gl.getUniformLocation(program, "iTime"),
90
+ iTimeDelta: gl.getUniformLocation(program, "iTimeDelta"),
91
+ iFrame: gl.getUniformLocation(program, "iFrame"),
92
+ iMouse: gl.getUniformLocation(program, "iMouse"),
93
+ iDate: gl.getUniformLocation(program, "iDate")
94
+ };
95
+ gl.useProgram(program);
96
+ return {
97
+ gl,
98
+ program,
99
+ locations,
100
+ time: 0,
101
+ frame: 0,
102
+ lastTime: 0
103
+ };
104
+ }
105
+ function render(state) {
106
+ const { gl } = state;
107
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
108
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
109
+ }
110
+ function dispose(state) {
111
+ const { gl, program } = state;
112
+ gl.deleteProgram(program);
113
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
114
+ }
115
+
116
+ // src/uniforms.ts
117
+ function updateUniforms(state, delta, speed, mouse) {
118
+ const { gl, locations } = state;
119
+ state.time += delta * speed;
120
+ if (locations.iTime) {
121
+ gl.uniform1f(locations.iTime, state.time);
122
+ }
123
+ if (locations.iTimeDelta) {
124
+ gl.uniform1f(locations.iTimeDelta, delta);
125
+ }
126
+ state.frame++;
127
+ if (locations.iFrame) {
128
+ gl.uniform1i(locations.iFrame, state.frame);
129
+ }
130
+ if (locations.iResolution) {
131
+ gl.uniform3f(
132
+ locations.iResolution,
133
+ gl.drawingBufferWidth,
134
+ gl.drawingBufferHeight,
135
+ 1
136
+ );
137
+ }
138
+ if (locations.iMouse) {
139
+ const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX);
140
+ const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY);
141
+ gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw);
142
+ }
143
+ if (locations.iDate) {
144
+ const now = /* @__PURE__ */ new Date();
145
+ const seconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1e3;
146
+ gl.uniform4f(
147
+ locations.iDate,
148
+ now.getFullYear(),
149
+ now.getMonth(),
150
+ // 0-based, matches Shadertoy
151
+ now.getDate(),
152
+ seconds
153
+ );
154
+ }
155
+ }
156
+
157
+ // src/useShadertoy.ts
158
+ function useShadertoy({
159
+ fragmentShader,
160
+ paused = false,
161
+ speed = 1,
162
+ pixelRatio,
163
+ mouse: mouseEnabled = true,
164
+ onError,
165
+ onLoad
166
+ }) {
167
+ const canvasRef = react.useRef(null);
168
+ const rendererRef = react.useRef(null);
169
+ const rafRef = react.useRef(0);
170
+ const pausedRef = react.useRef(paused);
171
+ const speedRef = react.useRef(speed);
172
+ const [isReady, setIsReady] = react.useState(false);
173
+ const [error, setError] = react.useState(null);
174
+ const mouseState = react.useRef({
175
+ x: 0,
176
+ y: 0,
177
+ clickX: 0,
178
+ clickY: 0,
179
+ pressed: false
180
+ });
181
+ pausedRef.current = paused;
182
+ speedRef.current = speed;
183
+ react.useEffect(() => {
184
+ const canvas = canvasRef.current;
185
+ if (!canvas) return;
186
+ const result = createRenderer(canvas, fragmentShader);
187
+ if (typeof result === "string") {
188
+ setError(result);
189
+ onError?.(result);
190
+ return;
191
+ }
192
+ rendererRef.current = result;
193
+ setIsReady(true);
194
+ setError(null);
195
+ onLoad?.();
196
+ let lastTimestamp = 0;
197
+ const loop = (timestamp) => {
198
+ const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1e3 : 0;
199
+ lastTimestamp = timestamp;
200
+ if (!pausedRef.current && rendererRef.current) {
201
+ updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current);
202
+ render(rendererRef.current);
203
+ }
204
+ rafRef.current = requestAnimationFrame(loop);
205
+ };
206
+ rafRef.current = requestAnimationFrame(loop);
207
+ return () => {
208
+ cancelAnimationFrame(rafRef.current);
209
+ if (rendererRef.current) {
210
+ dispose(rendererRef.current);
211
+ rendererRef.current = null;
212
+ }
213
+ setIsReady(false);
214
+ };
215
+ }, [fragmentShader, onError, onLoad]);
216
+ react.useEffect(() => {
217
+ const canvas = canvasRef.current;
218
+ if (!canvas) return;
219
+ const dpr = pixelRatio ?? (typeof window !== "undefined" ? window.devicePixelRatio : 1);
220
+ const observer = new ResizeObserver((entries) => {
221
+ for (const entry of entries) {
222
+ const { width, height } = entry.contentRect;
223
+ canvas.width = Math.round(width * dpr);
224
+ canvas.height = Math.round(height * dpr);
225
+ }
226
+ });
227
+ observer.observe(canvas);
228
+ return () => observer.disconnect();
229
+ }, [pixelRatio]);
230
+ react.useEffect(() => {
231
+ if (!mouseEnabled) return;
232
+ const canvas = canvasRef.current;
233
+ if (!canvas) return;
234
+ const toPixel = (cx, cy) => {
235
+ const r = canvas.getBoundingClientRect();
236
+ const dpr = pixelRatio ?? window.devicePixelRatio;
237
+ return {
238
+ x: (cx - r.left) * dpr,
239
+ y: (r.height - (cy - r.top)) * dpr
240
+ // flip Y
241
+ };
242
+ };
243
+ const onMove = (cx, cy) => {
244
+ if (!mouseState.current.pressed) return;
245
+ const { x, y } = toPixel(cx, cy);
246
+ mouseState.current.x = x;
247
+ mouseState.current.y = y;
248
+ };
249
+ const onDown = (cx, cy) => {
250
+ const { x, y } = toPixel(cx, cy);
251
+ mouseState.current.x = x;
252
+ mouseState.current.y = y;
253
+ mouseState.current.clickX = x;
254
+ mouseState.current.clickY = y;
255
+ mouseState.current.pressed = true;
256
+ };
257
+ const onUp = () => {
258
+ mouseState.current.pressed = false;
259
+ };
260
+ const mm = (e) => onMove(e.clientX, e.clientY);
261
+ const md = (e) => onDown(e.clientX, e.clientY);
262
+ const mu = () => onUp();
263
+ const tm = (e) => {
264
+ if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY);
265
+ };
266
+ const ts = (e) => {
267
+ if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY);
268
+ };
269
+ const te = () => onUp();
270
+ window.addEventListener("mousemove", mm);
271
+ canvas.addEventListener("mousedown", md);
272
+ window.addEventListener("mouseup", mu);
273
+ window.addEventListener("touchmove", tm, { passive: true });
274
+ canvas.addEventListener("touchstart", ts, { passive: true });
275
+ window.addEventListener("touchend", te);
276
+ return () => {
277
+ window.removeEventListener("mousemove", mm);
278
+ canvas.removeEventListener("mousedown", md);
279
+ window.removeEventListener("mouseup", mu);
280
+ window.removeEventListener("touchmove", tm);
281
+ canvas.removeEventListener("touchstart", ts);
282
+ window.removeEventListener("touchend", te);
283
+ };
284
+ }, [mouseEnabled, pixelRatio]);
285
+ const pause = react.useCallback(() => {
286
+ pausedRef.current = true;
287
+ }, []);
288
+ const resume = react.useCallback(() => {
289
+ pausedRef.current = false;
290
+ }, []);
291
+ return { canvasRef, isReady, error, pause, resume };
292
+ }
293
+ function Shadertoy({
294
+ fragmentShader,
295
+ style,
296
+ className,
297
+ paused,
298
+ speed,
299
+ pixelRatio,
300
+ mouse,
301
+ onError,
302
+ onLoad
303
+ }) {
304
+ const { canvasRef } = useShadertoy({
305
+ fragmentShader,
306
+ paused,
307
+ speed,
308
+ pixelRatio,
309
+ mouse,
310
+ onError,
311
+ onLoad
312
+ });
313
+ return /* @__PURE__ */ jsxRuntime.jsx(
314
+ "canvas",
315
+ {
316
+ ref: canvasRef,
317
+ className,
318
+ style: { width: "100%", height: "100%", display: "block", ...style }
319
+ }
320
+ );
321
+ }
322
+
323
+ exports.Shadertoy = Shadertoy;
324
+ exports.useShadertoy = useShadertoy;
325
+ //# sourceMappingURL=index.js.map
326
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/renderer.ts","../src/uniforms.ts","../src/useShadertoy.ts","../src/Shadertoy.tsx"],"names":["useRef","useState","useEffect","useCallback","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { RendererState, UniformLocations } from './types'\n\n// Full-screen quad: two triangles covering clip space\nconst QUAD_VERTICES = new Float32Array([\n -1, -1,\n 1, -1,\n -1, 1,\n -1, 1,\n 1, -1,\n 1, 1,\n])\n\nconst VERTEX_SHADER = `\nattribute vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`\n\n/**\n * Wrap Shadertoy GLSL: prepend uniform declarations + main() bridge.\n */\nfunction wrapFragmentShader(shader: string): string {\n return `precision highp float;\n\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform int iFrame;\nuniform vec4 iMouse;\nuniform vec4 iDate;\n\n${shader}\n\nvoid main() {\n mainImage(gl_FragColor, gl_FragCoord.xy);\n}\n`\n}\n\nfunction compileShader(\n gl: WebGLRenderingContext,\n type: number,\n source: string,\n): WebGLShader | string {\n const shader = gl.createShader(type)\n if (!shader) return 'Failed to create shader'\n\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) || 'Unknown compile error'\n gl.deleteShader(shader)\n return log\n }\n\n return shader\n}\n\n/**\n * Initialize WebGL: compile shaders, link program, setup quad.\n * Returns RendererState on success or error string on failure.\n */\nexport function createRenderer(\n canvas: HTMLCanvasElement,\n fragmentShader: string,\n): RendererState | string {\n const gl = canvas.getContext('webgl', {\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n })\n if (!gl) return 'WebGL not supported'\n\n // Compile vertex shader\n const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER)\n if (typeof vert === 'string') return vert\n\n // Compile fragment shader (wrapped)\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader))\n if (typeof frag === 'string') return frag\n\n // Link program\n const program = gl.createProgram()\n if (!program) return 'Failed to create program'\n\n gl.attachShader(program, vert)\n gl.attachShader(program, frag)\n gl.linkProgram(program)\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program) || 'Unknown link error'\n gl.deleteProgram(program)\n return log\n }\n\n // Clean up individual shaders (attached to program)\n gl.deleteShader(vert)\n gl.deleteShader(frag)\n\n // Setup quad geometry\n const buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW)\n\n const positionLoc = gl.getAttribLocation(program, 'position')\n gl.enableVertexAttribArray(positionLoc)\n gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)\n\n // Get uniform locations\n const locations: UniformLocations = {\n iResolution: gl.getUniformLocation(program, 'iResolution'),\n iTime: gl.getUniformLocation(program, 'iTime'),\n iTimeDelta: gl.getUniformLocation(program, 'iTimeDelta'),\n iFrame: gl.getUniformLocation(program, 'iFrame'),\n iMouse: gl.getUniformLocation(program, 'iMouse'),\n iDate: gl.getUniformLocation(program, 'iDate'),\n }\n\n gl.useProgram(program)\n\n return {\n gl,\n program,\n locations,\n time: 0,\n frame: 0,\n lastTime: 0,\n }\n}\n\n/**\n * Render one frame.\n */\nexport function render(state: RendererState): void {\n const { gl } = state\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)\n gl.drawArrays(gl.TRIANGLES, 0, 6)\n}\n\n/**\n * Clean up WebGL resources.\n */\nexport function dispose(state: RendererState): void {\n const { gl, program } = state\n gl.deleteProgram(program)\n gl.getExtension('WEBGL_lose_context')?.loseContext()\n}\n","import type { MouseState, RendererState } from './types'\n\n/**\n * Update all Shadertoy standard uniforms for one frame.\n */\nexport function updateUniforms(\n state: RendererState,\n delta: number,\n speed: number,\n mouse: MouseState,\n): void {\n const { gl, locations } = state\n\n // iTime\n state.time += delta * speed\n if (locations.iTime) {\n gl.uniform1f(locations.iTime, state.time)\n }\n\n // iTimeDelta\n if (locations.iTimeDelta) {\n gl.uniform1f(locations.iTimeDelta, delta)\n }\n\n // iFrame\n state.frame++\n if (locations.iFrame) {\n gl.uniform1i(locations.iFrame, state.frame)\n }\n\n // iResolution\n if (locations.iResolution) {\n gl.uniform3f(\n locations.iResolution,\n gl.drawingBufferWidth,\n gl.drawingBufferHeight,\n 1.0,\n )\n }\n\n // iMouse — Shadertoy convention:\n // xy: current position (if pressed)\n // zw: click position (positive when pressed, negative when released)\n if (locations.iMouse) {\n const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX)\n const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY)\n gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw)\n }\n\n // iDate — vec4(year, month, day, seconds_since_midnight)\n if (locations.iDate) {\n const now = new Date()\n const seconds =\n now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1000\n gl.uniform4f(\n locations.iDate,\n now.getFullYear(),\n now.getMonth(), // 0-based, matches Shadertoy\n now.getDate(),\n seconds,\n )\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { createRenderer, dispose, render } from './renderer'\nimport type { MouseState, RendererState, UseShadertoyOptions, UseShadertoyReturn } from './types'\nimport { updateUniforms } from './uniforms'\n\nexport function useShadertoy({\n fragmentShader,\n paused = false,\n speed = 1.0,\n pixelRatio,\n mouse: mouseEnabled = true,\n onError,\n onLoad,\n}: UseShadertoyOptions): UseShadertoyReturn {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const rendererRef = useRef<RendererState | null>(null)\n const rafRef = useRef<number>(0)\n const pausedRef = useRef(paused)\n const speedRef = useRef(speed)\n\n const [isReady, setIsReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const mouseState = useRef<MouseState>({\n x: 0, y: 0,\n clickX: 0, clickY: 0,\n pressed: false,\n })\n\n // Keep refs in sync\n pausedRef.current = paused\n speedRef.current = speed\n\n // Initialize WebGL\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const result = createRenderer(canvas, fragmentShader)\n if (typeof result === 'string') {\n setError(result)\n onError?.(result)\n return\n }\n\n rendererRef.current = result\n setIsReady(true)\n setError(null)\n onLoad?.()\n\n // Render loop\n let lastTimestamp = 0\n\n const loop = (timestamp: number) => {\n const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1000 : 0\n lastTimestamp = timestamp\n\n if (!pausedRef.current && rendererRef.current) {\n updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current)\n render(rendererRef.current)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n\n return () => {\n cancelAnimationFrame(rafRef.current)\n if (rendererRef.current) {\n dispose(rendererRef.current)\n rendererRef.current = null\n }\n setIsReady(false)\n }\n }, [fragmentShader, onError, onLoad])\n\n // Canvas resize\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const dpr = pixelRatio ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1)\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n canvas.width = Math.round(width * dpr)\n canvas.height = Math.round(height * dpr)\n }\n })\n\n observer.observe(canvas)\n return () => observer.disconnect()\n }, [pixelRatio])\n\n // Mouse / touch events\n useEffect(() => {\n if (!mouseEnabled) return\n const canvas = canvasRef.current\n if (!canvas) return\n\n const toPixel = (cx: number, cy: number) => {\n const r = canvas.getBoundingClientRect()\n const dpr = pixelRatio ?? window.devicePixelRatio\n return {\n x: (cx - r.left) * dpr,\n y: (r.height - (cy - r.top)) * dpr, // flip Y\n }\n }\n\n const onMove = (cx: number, cy: number) => {\n if (!mouseState.current.pressed) return\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n }\n\n const onDown = (cx: number, cy: number) => {\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n mouseState.current.clickX = x\n mouseState.current.clickY = y\n mouseState.current.pressed = true\n }\n\n const onUp = () => {\n mouseState.current.pressed = false\n }\n\n const mm = (e: MouseEvent) => onMove(e.clientX, e.clientY)\n const md = (e: MouseEvent) => onDown(e.clientX, e.clientY)\n const mu = () => onUp()\n const tm = (e: TouchEvent) => {\n if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY)\n }\n const ts = (e: TouchEvent) => {\n if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY)\n }\n const te = () => onUp()\n\n window.addEventListener('mousemove', mm)\n canvas.addEventListener('mousedown', md)\n window.addEventListener('mouseup', mu)\n window.addEventListener('touchmove', tm, { passive: true })\n canvas.addEventListener('touchstart', ts, { passive: true })\n window.addEventListener('touchend', te)\n\n return () => {\n window.removeEventListener('mousemove', mm)\n canvas.removeEventListener('mousedown', md)\n window.removeEventListener('mouseup', mu)\n window.removeEventListener('touchmove', tm)\n canvas.removeEventListener('touchstart', ts)\n window.removeEventListener('touchend', te)\n }\n }, [mouseEnabled, pixelRatio])\n\n const pause = useCallback(() => { pausedRef.current = true }, [])\n const resume = useCallback(() => { pausedRef.current = false }, [])\n\n return { canvasRef, isReady, error, pause, resume }\n}\n","import type { ShadertoyProps } from './types'\nimport { useShadertoy } from './useShadertoy'\n\nexport function Shadertoy({\n fragmentShader,\n style,\n className,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n}: ShadertoyProps) {\n const { canvasRef } = useShadertoy({\n fragmentShader,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n })\n\n return (\n <canvas\n ref={canvasRef}\n className={className}\n style={{ width: '100%', height: '100%', display: 'block', ...style }}\n />\n )\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,323 @@
1
+ import { useRef, useState, useEffect, useCallback } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/useShadertoy.ts
5
+
6
+ // src/renderer.ts
7
+ var QUAD_VERTICES = new Float32Array([
8
+ -1,
9
+ -1,
10
+ 1,
11
+ -1,
12
+ -1,
13
+ 1,
14
+ -1,
15
+ 1,
16
+ 1,
17
+ -1,
18
+ 1,
19
+ 1
20
+ ]);
21
+ var VERTEX_SHADER = `
22
+ attribute vec2 position;
23
+ void main() {
24
+ gl_Position = vec4(position, 0.0, 1.0);
25
+ }
26
+ `;
27
+ function wrapFragmentShader(shader) {
28
+ return `precision highp float;
29
+
30
+ uniform vec3 iResolution;
31
+ uniform float iTime;
32
+ uniform float iTimeDelta;
33
+ uniform int iFrame;
34
+ uniform vec4 iMouse;
35
+ uniform vec4 iDate;
36
+
37
+ ${shader}
38
+
39
+ void main() {
40
+ mainImage(gl_FragColor, gl_FragCoord.xy);
41
+ }
42
+ `;
43
+ }
44
+ function compileShader(gl, type, source) {
45
+ const shader = gl.createShader(type);
46
+ if (!shader) return "Failed to create shader";
47
+ gl.shaderSource(shader, source);
48
+ gl.compileShader(shader);
49
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
50
+ const log = gl.getShaderInfoLog(shader) || "Unknown compile error";
51
+ gl.deleteShader(shader);
52
+ return log;
53
+ }
54
+ return shader;
55
+ }
56
+ function createRenderer(canvas, fragmentShader) {
57
+ const gl = canvas.getContext("webgl", {
58
+ antialias: false,
59
+ alpha: true,
60
+ premultipliedAlpha: false
61
+ });
62
+ if (!gl) return "WebGL not supported";
63
+ const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
64
+ if (typeof vert === "string") return vert;
65
+ const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader));
66
+ if (typeof frag === "string") return frag;
67
+ const program = gl.createProgram();
68
+ if (!program) return "Failed to create program";
69
+ gl.attachShader(program, vert);
70
+ gl.attachShader(program, frag);
71
+ gl.linkProgram(program);
72
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
73
+ const log = gl.getProgramInfoLog(program) || "Unknown link error";
74
+ gl.deleteProgram(program);
75
+ return log;
76
+ }
77
+ gl.deleteShader(vert);
78
+ gl.deleteShader(frag);
79
+ const buffer = gl.createBuffer();
80
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
81
+ gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW);
82
+ const positionLoc = gl.getAttribLocation(program, "position");
83
+ gl.enableVertexAttribArray(positionLoc);
84
+ gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
85
+ const locations = {
86
+ iResolution: gl.getUniformLocation(program, "iResolution"),
87
+ iTime: gl.getUniformLocation(program, "iTime"),
88
+ iTimeDelta: gl.getUniformLocation(program, "iTimeDelta"),
89
+ iFrame: gl.getUniformLocation(program, "iFrame"),
90
+ iMouse: gl.getUniformLocation(program, "iMouse"),
91
+ iDate: gl.getUniformLocation(program, "iDate")
92
+ };
93
+ gl.useProgram(program);
94
+ return {
95
+ gl,
96
+ program,
97
+ locations,
98
+ time: 0,
99
+ frame: 0,
100
+ lastTime: 0
101
+ };
102
+ }
103
+ function render(state) {
104
+ const { gl } = state;
105
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
106
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
107
+ }
108
+ function dispose(state) {
109
+ const { gl, program } = state;
110
+ gl.deleteProgram(program);
111
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
112
+ }
113
+
114
+ // src/uniforms.ts
115
+ function updateUniforms(state, delta, speed, mouse) {
116
+ const { gl, locations } = state;
117
+ state.time += delta * speed;
118
+ if (locations.iTime) {
119
+ gl.uniform1f(locations.iTime, state.time);
120
+ }
121
+ if (locations.iTimeDelta) {
122
+ gl.uniform1f(locations.iTimeDelta, delta);
123
+ }
124
+ state.frame++;
125
+ if (locations.iFrame) {
126
+ gl.uniform1i(locations.iFrame, state.frame);
127
+ }
128
+ if (locations.iResolution) {
129
+ gl.uniform3f(
130
+ locations.iResolution,
131
+ gl.drawingBufferWidth,
132
+ gl.drawingBufferHeight,
133
+ 1
134
+ );
135
+ }
136
+ if (locations.iMouse) {
137
+ const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX);
138
+ const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY);
139
+ gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw);
140
+ }
141
+ if (locations.iDate) {
142
+ const now = /* @__PURE__ */ new Date();
143
+ const seconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1e3;
144
+ gl.uniform4f(
145
+ locations.iDate,
146
+ now.getFullYear(),
147
+ now.getMonth(),
148
+ // 0-based, matches Shadertoy
149
+ now.getDate(),
150
+ seconds
151
+ );
152
+ }
153
+ }
154
+
155
+ // src/useShadertoy.ts
156
+ function useShadertoy({
157
+ fragmentShader,
158
+ paused = false,
159
+ speed = 1,
160
+ pixelRatio,
161
+ mouse: mouseEnabled = true,
162
+ onError,
163
+ onLoad
164
+ }) {
165
+ const canvasRef = useRef(null);
166
+ const rendererRef = useRef(null);
167
+ const rafRef = useRef(0);
168
+ const pausedRef = useRef(paused);
169
+ const speedRef = useRef(speed);
170
+ const [isReady, setIsReady] = useState(false);
171
+ const [error, setError] = useState(null);
172
+ const mouseState = useRef({
173
+ x: 0,
174
+ y: 0,
175
+ clickX: 0,
176
+ clickY: 0,
177
+ pressed: false
178
+ });
179
+ pausedRef.current = paused;
180
+ speedRef.current = speed;
181
+ useEffect(() => {
182
+ const canvas = canvasRef.current;
183
+ if (!canvas) return;
184
+ const result = createRenderer(canvas, fragmentShader);
185
+ if (typeof result === "string") {
186
+ setError(result);
187
+ onError?.(result);
188
+ return;
189
+ }
190
+ rendererRef.current = result;
191
+ setIsReady(true);
192
+ setError(null);
193
+ onLoad?.();
194
+ let lastTimestamp = 0;
195
+ const loop = (timestamp) => {
196
+ const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1e3 : 0;
197
+ lastTimestamp = timestamp;
198
+ if (!pausedRef.current && rendererRef.current) {
199
+ updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current);
200
+ render(rendererRef.current);
201
+ }
202
+ rafRef.current = requestAnimationFrame(loop);
203
+ };
204
+ rafRef.current = requestAnimationFrame(loop);
205
+ return () => {
206
+ cancelAnimationFrame(rafRef.current);
207
+ if (rendererRef.current) {
208
+ dispose(rendererRef.current);
209
+ rendererRef.current = null;
210
+ }
211
+ setIsReady(false);
212
+ };
213
+ }, [fragmentShader, onError, onLoad]);
214
+ useEffect(() => {
215
+ const canvas = canvasRef.current;
216
+ if (!canvas) return;
217
+ const dpr = pixelRatio ?? (typeof window !== "undefined" ? window.devicePixelRatio : 1);
218
+ const observer = new ResizeObserver((entries) => {
219
+ for (const entry of entries) {
220
+ const { width, height } = entry.contentRect;
221
+ canvas.width = Math.round(width * dpr);
222
+ canvas.height = Math.round(height * dpr);
223
+ }
224
+ });
225
+ observer.observe(canvas);
226
+ return () => observer.disconnect();
227
+ }, [pixelRatio]);
228
+ useEffect(() => {
229
+ if (!mouseEnabled) return;
230
+ const canvas = canvasRef.current;
231
+ if (!canvas) return;
232
+ const toPixel = (cx, cy) => {
233
+ const r = canvas.getBoundingClientRect();
234
+ const dpr = pixelRatio ?? window.devicePixelRatio;
235
+ return {
236
+ x: (cx - r.left) * dpr,
237
+ y: (r.height - (cy - r.top)) * dpr
238
+ // flip Y
239
+ };
240
+ };
241
+ const onMove = (cx, cy) => {
242
+ if (!mouseState.current.pressed) return;
243
+ const { x, y } = toPixel(cx, cy);
244
+ mouseState.current.x = x;
245
+ mouseState.current.y = y;
246
+ };
247
+ const onDown = (cx, cy) => {
248
+ const { x, y } = toPixel(cx, cy);
249
+ mouseState.current.x = x;
250
+ mouseState.current.y = y;
251
+ mouseState.current.clickX = x;
252
+ mouseState.current.clickY = y;
253
+ mouseState.current.pressed = true;
254
+ };
255
+ const onUp = () => {
256
+ mouseState.current.pressed = false;
257
+ };
258
+ const mm = (e) => onMove(e.clientX, e.clientY);
259
+ const md = (e) => onDown(e.clientX, e.clientY);
260
+ const mu = () => onUp();
261
+ const tm = (e) => {
262
+ if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY);
263
+ };
264
+ const ts = (e) => {
265
+ if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY);
266
+ };
267
+ const te = () => onUp();
268
+ window.addEventListener("mousemove", mm);
269
+ canvas.addEventListener("mousedown", md);
270
+ window.addEventListener("mouseup", mu);
271
+ window.addEventListener("touchmove", tm, { passive: true });
272
+ canvas.addEventListener("touchstart", ts, { passive: true });
273
+ window.addEventListener("touchend", te);
274
+ return () => {
275
+ window.removeEventListener("mousemove", mm);
276
+ canvas.removeEventListener("mousedown", md);
277
+ window.removeEventListener("mouseup", mu);
278
+ window.removeEventListener("touchmove", tm);
279
+ canvas.removeEventListener("touchstart", ts);
280
+ window.removeEventListener("touchend", te);
281
+ };
282
+ }, [mouseEnabled, pixelRatio]);
283
+ const pause = useCallback(() => {
284
+ pausedRef.current = true;
285
+ }, []);
286
+ const resume = useCallback(() => {
287
+ pausedRef.current = false;
288
+ }, []);
289
+ return { canvasRef, isReady, error, pause, resume };
290
+ }
291
+ function Shadertoy({
292
+ fragmentShader,
293
+ style,
294
+ className,
295
+ paused,
296
+ speed,
297
+ pixelRatio,
298
+ mouse,
299
+ onError,
300
+ onLoad
301
+ }) {
302
+ const { canvasRef } = useShadertoy({
303
+ fragmentShader,
304
+ paused,
305
+ speed,
306
+ pixelRatio,
307
+ mouse,
308
+ onError,
309
+ onLoad
310
+ });
311
+ return /* @__PURE__ */ jsx(
312
+ "canvas",
313
+ {
314
+ ref: canvasRef,
315
+ className,
316
+ style: { width: "100%", height: "100%", display: "block", ...style }
317
+ }
318
+ );
319
+ }
320
+
321
+ export { Shadertoy, useShadertoy };
322
+ //# sourceMappingURL=index.mjs.map
323
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/renderer.ts","../src/uniforms.ts","../src/useShadertoy.ts","../src/Shadertoy.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { RendererState, UniformLocations } from './types'\n\n// Full-screen quad: two triangles covering clip space\nconst QUAD_VERTICES = new Float32Array([\n -1, -1,\n 1, -1,\n -1, 1,\n -1, 1,\n 1, -1,\n 1, 1,\n])\n\nconst VERTEX_SHADER = `\nattribute vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`\n\n/**\n * Wrap Shadertoy GLSL: prepend uniform declarations + main() bridge.\n */\nfunction wrapFragmentShader(shader: string): string {\n return `precision highp float;\n\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform int iFrame;\nuniform vec4 iMouse;\nuniform vec4 iDate;\n\n${shader}\n\nvoid main() {\n mainImage(gl_FragColor, gl_FragCoord.xy);\n}\n`\n}\n\nfunction compileShader(\n gl: WebGLRenderingContext,\n type: number,\n source: string,\n): WebGLShader | string {\n const shader = gl.createShader(type)\n if (!shader) return 'Failed to create shader'\n\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) || 'Unknown compile error'\n gl.deleteShader(shader)\n return log\n }\n\n return shader\n}\n\n/**\n * Initialize WebGL: compile shaders, link program, setup quad.\n * Returns RendererState on success or error string on failure.\n */\nexport function createRenderer(\n canvas: HTMLCanvasElement,\n fragmentShader: string,\n): RendererState | string {\n const gl = canvas.getContext('webgl', {\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n })\n if (!gl) return 'WebGL not supported'\n\n // Compile vertex shader\n const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER)\n if (typeof vert === 'string') return vert\n\n // Compile fragment shader (wrapped)\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader))\n if (typeof frag === 'string') return frag\n\n // Link program\n const program = gl.createProgram()\n if (!program) return 'Failed to create program'\n\n gl.attachShader(program, vert)\n gl.attachShader(program, frag)\n gl.linkProgram(program)\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program) || 'Unknown link error'\n gl.deleteProgram(program)\n return log\n }\n\n // Clean up individual shaders (attached to program)\n gl.deleteShader(vert)\n gl.deleteShader(frag)\n\n // Setup quad geometry\n const buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW)\n\n const positionLoc = gl.getAttribLocation(program, 'position')\n gl.enableVertexAttribArray(positionLoc)\n gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)\n\n // Get uniform locations\n const locations: UniformLocations = {\n iResolution: gl.getUniformLocation(program, 'iResolution'),\n iTime: gl.getUniformLocation(program, 'iTime'),\n iTimeDelta: gl.getUniformLocation(program, 'iTimeDelta'),\n iFrame: gl.getUniformLocation(program, 'iFrame'),\n iMouse: gl.getUniformLocation(program, 'iMouse'),\n iDate: gl.getUniformLocation(program, 'iDate'),\n }\n\n gl.useProgram(program)\n\n return {\n gl,\n program,\n locations,\n time: 0,\n frame: 0,\n lastTime: 0,\n }\n}\n\n/**\n * Render one frame.\n */\nexport function render(state: RendererState): void {\n const { gl } = state\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)\n gl.drawArrays(gl.TRIANGLES, 0, 6)\n}\n\n/**\n * Clean up WebGL resources.\n */\nexport function dispose(state: RendererState): void {\n const { gl, program } = state\n gl.deleteProgram(program)\n gl.getExtension('WEBGL_lose_context')?.loseContext()\n}\n","import type { MouseState, RendererState } from './types'\n\n/**\n * Update all Shadertoy standard uniforms for one frame.\n */\nexport function updateUniforms(\n state: RendererState,\n delta: number,\n speed: number,\n mouse: MouseState,\n): void {\n const { gl, locations } = state\n\n // iTime\n state.time += delta * speed\n if (locations.iTime) {\n gl.uniform1f(locations.iTime, state.time)\n }\n\n // iTimeDelta\n if (locations.iTimeDelta) {\n gl.uniform1f(locations.iTimeDelta, delta)\n }\n\n // iFrame\n state.frame++\n if (locations.iFrame) {\n gl.uniform1i(locations.iFrame, state.frame)\n }\n\n // iResolution\n if (locations.iResolution) {\n gl.uniform3f(\n locations.iResolution,\n gl.drawingBufferWidth,\n gl.drawingBufferHeight,\n 1.0,\n )\n }\n\n // iMouse — Shadertoy convention:\n // xy: current position (if pressed)\n // zw: click position (positive when pressed, negative when released)\n if (locations.iMouse) {\n const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX)\n const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY)\n gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw)\n }\n\n // iDate — vec4(year, month, day, seconds_since_midnight)\n if (locations.iDate) {\n const now = new Date()\n const seconds =\n now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1000\n gl.uniform4f(\n locations.iDate,\n now.getFullYear(),\n now.getMonth(), // 0-based, matches Shadertoy\n now.getDate(),\n seconds,\n )\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { createRenderer, dispose, render } from './renderer'\nimport type { MouseState, RendererState, UseShadertoyOptions, UseShadertoyReturn } from './types'\nimport { updateUniforms } from './uniforms'\n\nexport function useShadertoy({\n fragmentShader,\n paused = false,\n speed = 1.0,\n pixelRatio,\n mouse: mouseEnabled = true,\n onError,\n onLoad,\n}: UseShadertoyOptions): UseShadertoyReturn {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const rendererRef = useRef<RendererState | null>(null)\n const rafRef = useRef<number>(0)\n const pausedRef = useRef(paused)\n const speedRef = useRef(speed)\n\n const [isReady, setIsReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const mouseState = useRef<MouseState>({\n x: 0, y: 0,\n clickX: 0, clickY: 0,\n pressed: false,\n })\n\n // Keep refs in sync\n pausedRef.current = paused\n speedRef.current = speed\n\n // Initialize WebGL\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const result = createRenderer(canvas, fragmentShader)\n if (typeof result === 'string') {\n setError(result)\n onError?.(result)\n return\n }\n\n rendererRef.current = result\n setIsReady(true)\n setError(null)\n onLoad?.()\n\n // Render loop\n let lastTimestamp = 0\n\n const loop = (timestamp: number) => {\n const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1000 : 0\n lastTimestamp = timestamp\n\n if (!pausedRef.current && rendererRef.current) {\n updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current)\n render(rendererRef.current)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n\n return () => {\n cancelAnimationFrame(rafRef.current)\n if (rendererRef.current) {\n dispose(rendererRef.current)\n rendererRef.current = null\n }\n setIsReady(false)\n }\n }, [fragmentShader, onError, onLoad])\n\n // Canvas resize\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const dpr = pixelRatio ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1)\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n canvas.width = Math.round(width * dpr)\n canvas.height = Math.round(height * dpr)\n }\n })\n\n observer.observe(canvas)\n return () => observer.disconnect()\n }, [pixelRatio])\n\n // Mouse / touch events\n useEffect(() => {\n if (!mouseEnabled) return\n const canvas = canvasRef.current\n if (!canvas) return\n\n const toPixel = (cx: number, cy: number) => {\n const r = canvas.getBoundingClientRect()\n const dpr = pixelRatio ?? window.devicePixelRatio\n return {\n x: (cx - r.left) * dpr,\n y: (r.height - (cy - r.top)) * dpr, // flip Y\n }\n }\n\n const onMove = (cx: number, cy: number) => {\n if (!mouseState.current.pressed) return\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n }\n\n const onDown = (cx: number, cy: number) => {\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n mouseState.current.clickX = x\n mouseState.current.clickY = y\n mouseState.current.pressed = true\n }\n\n const onUp = () => {\n mouseState.current.pressed = false\n }\n\n const mm = (e: MouseEvent) => onMove(e.clientX, e.clientY)\n const md = (e: MouseEvent) => onDown(e.clientX, e.clientY)\n const mu = () => onUp()\n const tm = (e: TouchEvent) => {\n if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY)\n }\n const ts = (e: TouchEvent) => {\n if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY)\n }\n const te = () => onUp()\n\n window.addEventListener('mousemove', mm)\n canvas.addEventListener('mousedown', md)\n window.addEventListener('mouseup', mu)\n window.addEventListener('touchmove', tm, { passive: true })\n canvas.addEventListener('touchstart', ts, { passive: true })\n window.addEventListener('touchend', te)\n\n return () => {\n window.removeEventListener('mousemove', mm)\n canvas.removeEventListener('mousedown', md)\n window.removeEventListener('mouseup', mu)\n window.removeEventListener('touchmove', tm)\n canvas.removeEventListener('touchstart', ts)\n window.removeEventListener('touchend', te)\n }\n }, [mouseEnabled, pixelRatio])\n\n const pause = useCallback(() => { pausedRef.current = true }, [])\n const resume = useCallback(() => { pausedRef.current = false }, [])\n\n return { canvasRef, isReady, error, pause, resume }\n}\n","import type { ShadertoyProps } from './types'\nimport { useShadertoy } from './useShadertoy'\n\nexport function Shadertoy({\n fragmentShader,\n style,\n className,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n}: ShadertoyProps) {\n const { canvasRef } = useShadertoy({\n fragmentShader,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n })\n\n return (\n <canvas\n ref={canvasRef}\n className={className}\n style={{ width: '100%', height: '100%', display: 'block', ...style }}\n />\n )\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "react-shadertoy",
3
+ "version": "0.1.0",
4
+ "description": "Run Shadertoy GLSL shaders in React. Copy-paste and it works.",
5
+ "author": "Wrennly (https://github.com/wrennly)",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/wrennly/react-shadertoy",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/wrennly/react-shadertoy"
11
+ },
12
+ "keywords": [
13
+ "react",
14
+ "shadertoy",
15
+ "glsl",
16
+ "webgl",
17
+ "shader",
18
+ "fragment-shader",
19
+ "creative-coding",
20
+ "generative-art",
21
+ "webgpu",
22
+ "three.js"
23
+ ],
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.mjs",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.mjs",
31
+ "require": "./dist/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch"
41
+ },
42
+ "peerDependencies": {
43
+ "react": ">=18.0.0",
44
+ "react-dom": ">=18.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/react": "^19.2.14",
48
+ "react": "^19.2.4",
49
+ "react-dom": "^19.2.4",
50
+ "tsup": "^8.5.1",
51
+ "typescript": "^5.9.3"
52
+ }
53
+ }