@playfast/reform-better-hub-ui-primitives 0.2.2 → 0.3.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.
@@ -1,6 +1,15 @@
1
- import type * as React from "react";
2
- import { useEffect, useRef } from "react";
3
- import { CLICK_DECAY, FULLSCREEN_VERTEX_COUNT, MOUSE_LERP, MOUSE_LERP_IDLE, MS_PER_SECOND, RENDER_SCALE, type ShaderSpec, VERTEX_SHADER } from "./halftone-config";
1
+ import type * as React from 'react'
2
+ import { useEffect, useRef } from 'react'
3
+ import {
4
+ CLICK_DECAY,
5
+ FULLSCREEN_VERTEX_COUNT,
6
+ MOUSE_LERP,
7
+ MOUSE_LERP_IDLE,
8
+ MS_PER_SECOND,
9
+ RENDER_SCALE,
10
+ type ShaderSpec,
11
+ VERTEX_SHADER,
12
+ } from './halftone-config'
4
13
 
5
14
  const FRAGMENT_SHADER = `
6
15
  precision highp float;
@@ -129,158 +138,158 @@ void main() {
129
138
 
130
139
  float col = noise * vignette + grid;
131
140
  gl_FragColor = vec4(vec3(col), 1.0);
132
- }`;
141
+ }`
133
142
 
134
143
  export function HalftoneBackground(): React.JSX.Element {
135
- const canvasRef = useRef<HTMLCanvasElement>(null);
136
- const wrapperRef = useRef<HTMLDivElement>(null);
137
- const frameRef = useRef<number>(0);
138
- const mouseRef = useRef({ x: -1, y: -1 });
139
- const smoothMouseRef = useRef({ x: -1, y: -1 });
140
- const clickRef = useRef({ strength: 0, x: 0, y: 0 });
141
-
142
- useEffect(() => {
143
- const canvas = canvasRef.current;
144
- const wrapper = wrapperRef.current;
145
- if (!canvas || !wrapper) {
146
- return;
147
- }
148
-
149
- const glContext = canvas.getContext("webgl", {
150
- alpha: false,
151
- antialias: false,
152
- preserveDrawingBuffer: false,
153
- });
154
- if (!glContext) {
155
- return;
156
- }
157
-
158
- const createShader = ({ type, source }: ShaderSpec) => {
159
- const shader = glContext!.createShader(type)!;
160
- glContext!.shaderSource(shader, source);
161
- glContext!.compileShader(shader);
162
- return shader;
163
- };
164
-
165
- const vertexShader = createShader({
166
- type: glContext.VERTEX_SHADER,
167
- source: VERTEX_SHADER,
168
- });
169
- const fragmentShader = createShader({
170
- type: glContext.FRAGMENT_SHADER,
171
- source: FRAGMENT_SHADER,
172
- });
173
-
174
- const program = glContext.createProgram()!;
175
- glContext.attachShader(program, vertexShader);
176
- glContext.attachShader(program, fragmentShader);
177
- glContext.linkProgram(program);
178
- glContext.useProgram(program);
179
-
180
- const buffer = glContext.createBuffer();
181
- glContext.bindBuffer(glContext.ARRAY_BUFFER, buffer);
182
- glContext.bufferData(
183
- glContext.ARRAY_BUFFER,
184
- new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
185
- glContext.STATIC_DRAW,
186
- );
187
-
188
- const aPosition = glContext.getAttribLocation(program, "a_position");
189
- glContext.enableVertexAttribArray(aPosition);
190
- glContext.vertexAttribPointer(aPosition, 2, glContext.FLOAT, false, 0, 0);
191
-
192
- const uTime = glContext.getUniformLocation(program, "u_time");
193
- const uResolution = glContext.getUniformLocation(program, "u_resolution");
194
- const uMouse = glContext.getUniformLocation(program, "u_mouse");
195
- const uClick = glContext.getUniformLocation(program, "u_click");
196
- const uClickPos = glContext.getUniformLocation(program, "u_clickPos");
197
-
198
- const startTime = performance.now();
199
-
200
- const resize = () => {
201
- const dpr = Math.min(window.devicePixelRatio || 1, 2);
202
- const scale = RENDER_SCALE * dpr;
203
- canvas.width = canvas.offsetWidth * scale;
204
- canvas.height = canvas.offsetHeight * scale;
205
- glContext.viewport(0, 0, canvas.width, canvas.height);
206
- };
207
-
208
- resize();
209
- window.addEventListener("resize", resize);
210
-
211
- const onMouseMove = (event: MouseEvent) => {
212
- const rect = wrapper.getBoundingClientRect();
213
- mouseRef.current.x = (event.clientX - rect.left) / rect.width;
214
- mouseRef.current.y = 1.0 - (event.clientY - rect.top) / rect.height;
215
- };
216
-
217
- const onMouseLeave = () => {
218
- mouseRef.current.x = -1;
219
- mouseRef.current.y = -1;
220
- };
221
-
222
- const onClick = (event: MouseEvent) => {
223
- const rect = wrapper.getBoundingClientRect();
224
- clickRef.current.x = (event.clientX - rect.left) / rect.width;
225
- clickRef.current.y = 1.0 - (event.clientY - rect.top) / rect.height;
226
- clickRef.current.strength = 1.0;
227
- };
228
-
229
- window.addEventListener("mousemove", onMouseMove, { passive: true });
230
- wrapper.addEventListener("mouseleave", onMouseLeave);
231
- wrapper.addEventListener("click", onClick);
232
-
233
- const draw = () => {
234
- const elapsed = (performance.now() - startTime) / MS_PER_SECOND;
235
-
236
- const target = mouseRef.current;
237
- const smooth = smoothMouseRef.current;
238
- if (target.x < 0) {
239
- smooth.x += (target.x - smooth.x) * MOUSE_LERP_IDLE;
240
- smooth.y += (target.y - smooth.y) * MOUSE_LERP_IDLE;
241
- } else {
242
- smooth.x += (target.x - smooth.x) * MOUSE_LERP;
243
- smooth.y += (target.y - smooth.y) * MOUSE_LERP;
244
- }
245
-
246
- clickRef.current.strength *= CLICK_DECAY;
247
-
248
- glContext.uniform1f(uTime, elapsed);
249
- glContext.uniform2f(uResolution, canvas.width, canvas.height);
250
- glContext.uniform2f(uMouse, smooth.x, smooth.y);
251
- glContext.uniform1f(uClick, clickRef.current.strength);
252
- glContext.uniform2f(uClickPos, clickRef.current.x, clickRef.current.y);
253
- glContext.drawArrays(glContext.TRIANGLES, 0, FULLSCREEN_VERTEX_COUNT);
254
- frameRef.current = requestAnimationFrame(draw);
255
- };
256
-
257
- frameRef.current = requestAnimationFrame(draw);
258
-
259
- return () => {
260
- cancelAnimationFrame(frameRef.current);
261
- window.removeEventListener("resize", resize);
262
- window.removeEventListener("mousemove", onMouseMove);
263
- wrapper.removeEventListener("mouseleave", onMouseLeave);
264
- wrapper.removeEventListener("click", onClick);
265
- glContext.deleteProgram(program);
266
- glContext.deleteShader(vertexShader);
267
- glContext.deleteShader(fragmentShader);
268
- glContext.deleteBuffer(buffer);
269
- };
270
- }, []);
271
-
272
- return (
273
- <div
274
- ref={wrapperRef}
275
- className="absolute inset-0 overflow-hidden"
276
- style={{ background: "var(--shader-bg)" }}
277
- aria-hidden="true"
278
- >
279
- <canvas
280
- ref={canvasRef}
281
- className="w-full h-full"
282
- style={{ filter: "var(--shader-filter)" }}
283
- />
284
- </div>
285
- );
144
+ const canvasRef = useRef<HTMLCanvasElement>(null)
145
+ const wrapperRef = useRef<HTMLDivElement>(null)
146
+ const frameRef = useRef<number>(0)
147
+ const mouseRef = useRef({ x: -1, y: -1 })
148
+ const smoothMouseRef = useRef({ x: -1, y: -1 })
149
+ const clickRef = useRef({ strength: 0, x: 0, y: 0 })
150
+
151
+ useEffect(() => {
152
+ const canvas = canvasRef.current
153
+ const wrapper = wrapperRef.current
154
+ if (!canvas || !wrapper) {
155
+ return
156
+ }
157
+
158
+ const glContext = canvas.getContext('webgl', {
159
+ alpha: false,
160
+ antialias: false,
161
+ preserveDrawingBuffer: false,
162
+ })
163
+ if (!glContext) {
164
+ return
165
+ }
166
+
167
+ const createShader = ({ type, source }: ShaderSpec) => {
168
+ const shader = glContext!.createShader(type)!
169
+ glContext!.shaderSource(shader, source)
170
+ glContext!.compileShader(shader)
171
+ return shader
172
+ }
173
+
174
+ const vertexShader = createShader({
175
+ type: glContext.VERTEX_SHADER,
176
+ source: VERTEX_SHADER,
177
+ })
178
+ const fragmentShader = createShader({
179
+ type: glContext.FRAGMENT_SHADER,
180
+ source: FRAGMENT_SHADER,
181
+ })
182
+
183
+ const program = glContext.createProgram()!
184
+ glContext.attachShader(program, vertexShader)
185
+ glContext.attachShader(program, fragmentShader)
186
+ glContext.linkProgram(program)
187
+ glContext.useProgram(program)
188
+
189
+ const buffer = glContext.createBuffer()
190
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, buffer)
191
+ glContext.bufferData(
192
+ glContext.ARRAY_BUFFER,
193
+ new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
194
+ glContext.STATIC_DRAW,
195
+ )
196
+
197
+ const aPosition = glContext.getAttribLocation(program, 'a_position')
198
+ glContext.enableVertexAttribArray(aPosition)
199
+ glContext.vertexAttribPointer(aPosition, 2, glContext.FLOAT, false, 0, 0)
200
+
201
+ const uTime = glContext.getUniformLocation(program, 'u_time')
202
+ const uResolution = glContext.getUniformLocation(program, 'u_resolution')
203
+ const uMouse = glContext.getUniformLocation(program, 'u_mouse')
204
+ const uClick = glContext.getUniformLocation(program, 'u_click')
205
+ const uClickPos = glContext.getUniformLocation(program, 'u_clickPos')
206
+
207
+ const startTime = performance.now()
208
+
209
+ const resize = () => {
210
+ const dpr = Math.min(window.devicePixelRatio || 1, 2)
211
+ const scale = RENDER_SCALE * dpr
212
+ canvas.width = canvas.offsetWidth * scale
213
+ canvas.height = canvas.offsetHeight * scale
214
+ glContext.viewport(0, 0, canvas.width, canvas.height)
215
+ }
216
+
217
+ resize()
218
+ window.addEventListener('resize', resize)
219
+
220
+ const onMouseMove = (event: MouseEvent) => {
221
+ const rect = wrapper.getBoundingClientRect()
222
+ mouseRef.current.x = (event.clientX - rect.left) / rect.width
223
+ mouseRef.current.y = 1.0 - (event.clientY - rect.top) / rect.height
224
+ }
225
+
226
+ const onMouseLeave = () => {
227
+ mouseRef.current.x = -1
228
+ mouseRef.current.y = -1
229
+ }
230
+
231
+ const onClick = (event: MouseEvent) => {
232
+ const rect = wrapper.getBoundingClientRect()
233
+ clickRef.current.x = (event.clientX - rect.left) / rect.width
234
+ clickRef.current.y = 1.0 - (event.clientY - rect.top) / rect.height
235
+ clickRef.current.strength = 1.0
236
+ }
237
+
238
+ window.addEventListener('mousemove', onMouseMove, { passive: true })
239
+ wrapper.addEventListener('mouseleave', onMouseLeave)
240
+ wrapper.addEventListener('click', onClick)
241
+
242
+ const draw = () => {
243
+ const elapsed = (performance.now() - startTime) / MS_PER_SECOND
244
+
245
+ const target = mouseRef.current
246
+ const smooth = smoothMouseRef.current
247
+ if (target.x < 0) {
248
+ smooth.x += (target.x - smooth.x) * MOUSE_LERP_IDLE
249
+ smooth.y += (target.y - smooth.y) * MOUSE_LERP_IDLE
250
+ } else {
251
+ smooth.x += (target.x - smooth.x) * MOUSE_LERP
252
+ smooth.y += (target.y - smooth.y) * MOUSE_LERP
253
+ }
254
+
255
+ clickRef.current.strength *= CLICK_DECAY
256
+
257
+ glContext.uniform1f(uTime, elapsed)
258
+ glContext.uniform2f(uResolution, canvas.width, canvas.height)
259
+ glContext.uniform2f(uMouse, smooth.x, smooth.y)
260
+ glContext.uniform1f(uClick, clickRef.current.strength)
261
+ glContext.uniform2f(uClickPos, clickRef.current.x, clickRef.current.y)
262
+ glContext.drawArrays(glContext.TRIANGLES, 0, FULLSCREEN_VERTEX_COUNT)
263
+ frameRef.current = requestAnimationFrame(draw)
264
+ }
265
+
266
+ frameRef.current = requestAnimationFrame(draw)
267
+
268
+ return () => {
269
+ cancelAnimationFrame(frameRef.current)
270
+ window.removeEventListener('resize', resize)
271
+ window.removeEventListener('mousemove', onMouseMove)
272
+ wrapper.removeEventListener('mouseleave', onMouseLeave)
273
+ wrapper.removeEventListener('click', onClick)
274
+ glContext.deleteProgram(program)
275
+ glContext.deleteShader(vertexShader)
276
+ glContext.deleteShader(fragmentShader)
277
+ glContext.deleteBuffer(buffer)
278
+ }
279
+ }, [])
280
+
281
+ return (
282
+ <div
283
+ ref={wrapperRef}
284
+ className="absolute inset-0 overflow-hidden"
285
+ style={{ background: 'var(--shader-bg)' }}
286
+ aria-hidden="true"
287
+ >
288
+ <canvas
289
+ ref={canvasRef}
290
+ className="w-full h-full"
291
+ style={{ filter: 'var(--shader-filter)' }}
292
+ />
293
+ </div>
294
+ )
286
295
  }
@@ -2,13 +2,13 @@ export const VERTEX_SHADER = `
2
2
  attribute vec2 a_position;
3
3
  void main() {
4
4
  gl_Position = vec4(a_position, 0.0, 1.0);
5
- }`;
5
+ }`
6
6
 
7
- export const RENDER_SCALE = 0.5;
8
- export const MS_PER_SECOND = 1000;
9
- export const MOUSE_LERP = 0.08;
10
- export const MOUSE_LERP_IDLE = 0.02;
11
- export const CLICK_DECAY = 0.96;
12
- export const FULLSCREEN_VERTEX_COUNT = 6;
7
+ export const RENDER_SCALE = 0.5
8
+ export const MS_PER_SECOND = 1000
9
+ export const MOUSE_LERP = 0.08
10
+ export const MOUSE_LERP_IDLE = 0.02
11
+ export const CLICK_DECAY = 0.96
12
+ export const FULLSCREEN_VERTEX_COUNT = 6
13
13
 
14
- export type ShaderSpec = { readonly type: number; readonly source: string };
14
+ export type ShaderSpec = { readonly type: number; readonly source: string }