Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update hooks #6

Merged
merged 2 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 5 additions & 25 deletions lib/playground/src/pages/pointer/blob.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Layout from "../../layouts/Layout.astro";
---

<script>
import { onPointerEvents, useLoop, useWebGLCanvas } from "usegl";
import { usePointerEvents, useLoop, useWebGLCanvas } from "usegl";
import { fragment, vertex } from "../../shaders/blob";
import { incrementRenderCount } from "../../components/renderCount";

Expand All @@ -20,13 +20,12 @@ import Layout from "../../layouts/Layout.astro";
immediate: false,
});

const canvasGeometry = getCanvasGeometry(canvas);
const targetPointer = { x: 0, y: 0 };

onPointerEvents(canvas, {
move: ({ x, y }) => {
targetPointer.x = (x - canvasGeometry.center.x) / (canvasGeometry.width / 2);
targetPointer.y = (y - canvasGeometry.center.y) / (canvasGeometry.height / 2);
usePointerEvents(canvas, {
move: ({ pointer, canvasRect, canvasCenter }) => {
targetPointer.x = (pointer.x - canvasCenter.x) / (canvasRect.width / 2);
targetPointer.y = (canvasCenter.y - pointer.y) / (canvasRect.height / 2);
},
leave: () => {
targetPointer.x = targetPointer.y = 0;
Expand All @@ -45,25 +44,6 @@ import Layout from "../../layouts/Layout.astro";
{ immediate: false }
);

function getCanvasGeometry(canvas: HTMLCanvasElement) {
const canvasGeometry = { center: { x: 0, y: 0 }, width: 0, height: 0 };

function updateGeometry() {
const { top, right, bottom, left } = canvas.getBoundingClientRect();
canvasGeometry.center = {
x: (left + right) / 2,
y: (top + bottom) / 2,
};
canvasGeometry.width = right - left;
canvasGeometry.height = top - bottom;
}

updateGeometry();
window.addEventListener("resize", updateGeometry);

return canvasGeometry;
}

onAfterRender(incrementRenderCount);
</script>

Expand Down
24 changes: 16 additions & 8 deletions lib/playground/src/pages/pointer/pointer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Layout from "../../layouts/Layout.astro";
---

<script>
import { onPointerEvents, useWebGLCanvas } from "usegl";
import { usePointerEvents, useWebGLCanvas } from "usegl";
import { incrementRenderCount } from "../../components/renderCount";

const canvas = document.querySelector("canvas");
Expand All @@ -14,27 +14,35 @@ import Layout from "../../layouts/Layout.astro";
varying vec2 vUv;
uniform vec2 uPointerPosition;
uniform vec2 uResolution;
uniform vec3 uCircleColor;

void main() {
vec2 uv = (vUv - .5) * uResolution / min(uResolution.x, uResolution.y);
float dist = distance(uv, uPointerPosition);
float circle = 1. - smoothstep(.099, .101, dist);
gl_FragColor = vec4(vec3(circle), 1.);
float circleMask = 1. - smoothstep(.099, .101, dist);
vec3 circle = mix(vec3(0.), uCircleColor, circleMask);
gl_FragColor = vec4(circle, 1.);
}
`,
uniforms: {
uPointerPosition: [0, 0],
uCircleColor: [1, 1, 1],
},
});

onPointerEvents(canvas, {
move: ({ clientX, clientY }) => {
const { left, bottom, width, height } = canvas.getBoundingClientRect();
usePointerEvents(canvas, {
move: ({ pointer, canvasCenter, canvasRect }) => {
uniforms.uPointerPosition = [
(clientX - (left + width / 2)) / Math.min(width, height),
(bottom - height / 2 - clientY) / Math.min(width, height),
(pointer.x - canvasCenter.x) / Math.min(canvasRect.width, canvasRect.height),
(canvasCenter.y - pointer.y) / Math.min(canvasRect.width, canvasRect.height),
];
},
down: () => {
uniforms.uCircleColor = [1, 0, 0];
},
up: () => {
uniforms.uCircleColor = [1, 1, 1];
},
});

onAfterRender(incrementRenderCount);
Expand Down
34 changes: 0 additions & 34 deletions lib/src/helpers/pointer.ts

This file was deleted.

72 changes: 72 additions & 0 deletions lib/src/hooks/useBoundingRect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useResizeObserver } from "./useResizeObserver";

export interface UseBoundingRectOptions {
/**
* Listen to window resize event
*
* @default true
*/
windowResize?: boolean;
/**
* Listen to window scroll event
*
* @default true
*/
windowScroll?: boolean;
}

export interface BoundingRect {
width: number;
height: number;
top: number;
right: number;
bottom: number;
left: number;
x: number;
y: number;
}

/**
* Dynamically get the bounding rectangle of an HTML element
*/
export function useBoundingRect(target: HTMLElement, options: UseBoundingRectOptions = {}) {
const {
windowResize = typeof window !== "undefined",
windowScroll = typeof window !== "undefined",
} = options;

const rect: BoundingRect = {
width: 0,
height: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
x: 0,
y: 0,
};

const center = { x: 0, y: 0 };

function update() {
const newRect = target.getBoundingClientRect();

// update the rect object instead of reassagning to allow destructuring the output of the function
for (const key of Object.keys(rect) as Array<keyof typeof rect>) {
rect[key] = newRect[key];
}

center.x = (rect.left + rect.right) / 2;
center.y = (rect.top + rect.bottom) / 2;
}

useResizeObserver(target, update);

if (windowScroll) window.addEventListener("scroll", update, { capture: true, passive: true });
if (windowResize) window.addEventListener("resize", update, { passive: true });

return {
rect: rect as Readonly<typeof rect>,
center: center as Readonly<typeof center>,
};
}
7 changes: 5 additions & 2 deletions lib/src/hooks/useLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface LoopData {
elapsedTime: number;
}

export interface LoopOptions {
export interface UseLoopOptions {
/**
* If true, the loop will start immediately.
*
Expand All @@ -40,7 +40,10 @@ const allLoops: Array<LoopObj> = [];
* @param options Options for the loop.
* @returns An object with `play` and `pause` methods to control the animation loop.
*/
export function useLoop(callback: ({ time, deltaTime }: LoopData) => void, options?: LoopOptions) {
export function useLoop(
callback: ({ time, deltaTime }: LoopData) => void,
options?: UseLoopOptions,
) {
let animationFrameHandle: number;
let pauseTime: number | null;
let loopStartTime: number;
Expand Down
62 changes: 62 additions & 0 deletions lib/src/hooks/usePointerEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { BoundingRect } from "./useBoundingRect";
import { useBoundingRect } from "./useBoundingRect";

type HandlerArgs = {
pointer: {
x: number;
y: number;
};
canvasRect: BoundingRect;
canvasCenter: {
x: number;
y: number;
};
};

type PointerEventsHandlers = {
enter?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void;
move?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void;
leave?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void;
down?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void;
up?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void;
};

/**
* Listen to common pointer events and provide additional infos about the canvas
*/
export function usePointerEvents(canvas: HTMLCanvasElement, handlers: PointerEventsHandlers) {
const { rect: canvasRect, center: canvasCenter } = useBoundingRect(canvas);

const activeHandlers = Object.fromEntries(
Object.entries(handlers)
.filter(([, handler]) => typeof handler === "function")
.map(([handlerName, handlerFunction]) => [
handlerName,
(e: PointerEvent) => {
handlerFunction({
pointer: { x: e.clientX, y: e.clientY },
canvasRect,
canvasCenter,
});
},
]),
);

function listen() {
for (const [event, handler] of Object.entries(activeHandlers)) {
canvas.addEventListener(`pointer${event as keyof PointerEventsHandlers}`, handler, {
passive: true,
});
}
}

function stop() {
for (const [event, handler] of Object.entries(activeHandlers)) {
canvas.removeEventListener(`pointer${event as keyof PointerEventsHandlers}`, handler);
}
}

listen();

return { stop, listen };
}
4 changes: 2 additions & 2 deletions lib/src/hooks/useWebGLCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useWebGLContext } from "./useWebGLContext";
import { useQuadRenderPass } from "./useQuadRenderPass";
import { useCompositor } from "./useCompositor";
import { findUniformName } from "../internal/findName";
import type { LoopOptions } from "./useLoop";
import type { UseLoopOptions } from "./useLoop";
import { useLoop } from "./useLoop";

interface Props<U extends Uniforms> extends LoopOptions {
interface Props<U extends Uniforms> extends UseLoopOptions {
canvas: HTMLCanvasElement | OffscreenCanvas | string;
fragment: string;
vertex?: string;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export { useWebGLCanvas } from "./hooks/useWebGLCanvas";
export { useWebGLContext } from "./hooks/useWebGLContext";
export { useLoop, playAllLoops, pauseAllLoops } from "./hooks/useLoop";
export { useResizeObserver } from "./hooks/useResizeObserver";

export { onPointerEvents } from "./helpers/pointer";
export { useBoundingRect } from "./hooks/useBoundingRect";
export { usePointerEvents } from "./hooks/usePointerEvents";
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions lib/tests/screenshots.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ test("pointer move", async ({ page, viewport, baseURL }) => {
await page.mouse.move((viewport?.width || 0) * 0.5, (viewport?.height || 0) * 0.45);

await expect(page.getByText("Renders: 2")).toBeVisible();

await page.mouse.down();

await page.waitForTimeout(100);

await expect(page.locator("main")).toHaveScreenshot();
Expand Down