From 530a145683b57140820de22f1e9c2787306a4708 Mon Sep 17 00:00:00 2001 From: Liam Egan Date: Tue, 25 Aug 2026 07:28:22 -0700 Subject: [PATCH 1/2] Adding resize observer to the canvas element to ensure transformational metrics accurately match the provided canvas on devices that use viewport modification. --- packages/react/package.json | 2 +- .../scroll-renderer-provider.tsx | 2 +- packages/site/vite.config.js | 3 +- packages/wtc-gl/package.json | 2 +- .../recipes/ScrollRenderer/ScrollRenderer.ts | 38 ++++++++++++++----- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index 0dc1ef4..3e32cbb 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@wethegit/react-wtc-gl", - "version": "0.1.0", + "version": "0.1.1", "description": "React components and hooks for wtc-gl recipes.", "type": "module", "files": [ diff --git a/packages/react/src/scroll-renderer/scroll-renderer-provider.tsx b/packages/react/src/scroll-renderer/scroll-renderer-provider.tsx index 2b22ced..83515f5 100644 --- a/packages/react/src/scroll-renderer/scroll-renderer-provider.tsx +++ b/packages/react/src/scroll-renderer/scroll-renderer-provider.tsx @@ -39,7 +39,7 @@ const defaultCanvasStyle: CSSProperties = { position: 'fixed', inset: 0, width: '100%', - height: '100%', + height: '100lvh', pointerEvents: 'none', zIndex: 0 } diff --git a/packages/site/vite.config.js b/packages/site/vite.config.js index d66c9ce..be8ee7f 100644 --- a/packages/site/vite.config.js +++ b/packages/site/vite.config.js @@ -15,5 +15,6 @@ export default defineConfig({ '@wethegit/react-wtc-gl': resolvePath('../react/src/index.ts') }, dedupe: ['react', 'react-dom'] - } + }, + server: { host: true } }) diff --git a/packages/wtc-gl/package.json b/packages/wtc-gl/package.json index c0d24ea..3f4f0af 100644 --- a/packages/wtc-gl/package.json +++ b/packages/wtc-gl/package.json @@ -1,6 +1,6 @@ { "name": "wtc-gl", - "version": "1.3.0", + "version": "1.3.1", "description": "Typescript simple Web GL library.", "type": "module", "files": [ diff --git a/packages/wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts b/packages/wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts index d8c9884..bfa5fd8 100644 --- a/packages/wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts +++ b/packages/wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts @@ -79,6 +79,7 @@ export class ScrollRenderer { #playing: boolean = false #ownsCanvas: boolean #cleared: boolean = false + #resizeObserver: ResizeObserver | null = null constructor({ rendererProps = {}, @@ -99,6 +100,10 @@ export class ScrollRenderer { this.render = this.render.bind(this) this.resize = this.resize.bind(this) + if (typeof ResizeObserver !== 'undefined') { + this.#resizeObserver = new ResizeObserver(this.resize) + this.#resizeObserver.observe(this.canvas) + } window.addEventListener('resize', this.resize) this.resize() } @@ -109,19 +114,29 @@ export class ScrollRenderer { } /** - * Synchronises the GL canvas buffer size with the layout viewport. + * Synchronises the GL canvas buffer size with the canvas element's CSS size. * - * Uses `document.documentElement.clientWidth/clientHeight` rather than - * `window.innerWidth/innerHeight` because on systems with classic - * (non-overlay) scrollbars `innerWidth` includes the scrollbar gutter, - * while a `position:fixed; width:100%` canvas does not — causing every - * scissor rect to clip a few pixels short on the trailing edge. + * Measures the canvas (`clientWidth`/`clientHeight`) so the buffer + * can never disagree with how the element is laid out. Falls back to the + * document's client size when the canvas isn't in the DOM yet (or has no + * layout size), which also avoids the scrollbar-gutter offset that + * `window.innerWidth/innerHeight` would introduce. * - * Called automatically on construction and on every `resize` event. + * Called automatically on construction, whenever a canvas element's size + * changes, and on every `resize` event. */ resize() { - const el = document.documentElement - this.renderer.dimensions = new Vec2(el.clientWidth, el.clientHeight) + const canvas = this.canvas + let width = canvas.clientWidth + let height = canvas.clientHeight + if (!width || !height) { + const el = document.documentElement + width = el.clientWidth + height = el.clientHeight + } + const current = this.renderer.dimensions + if (current && current.width === width && current.height === height) return + this.renderer.dimensions = new Vec2(width, height) } /** @@ -177,6 +192,9 @@ export class ScrollRenderer { } this.#cleared = false + // Catch element size changes that haven't been observed yet + this.resize() + const { gl } = this const { dpr } = this.renderer const canvasWidth = this.renderer.dimensions.width * dpr @@ -289,6 +307,8 @@ export class ScrollRenderer { destroy(): HTMLCanvasElement { this.playing = false window.removeEventListener('resize', this.resize) + this.#resizeObserver?.disconnect() + this.#resizeObserver = null this.#scenes.forEach((s) => s.destroy()) this.#scenes = [] if (this.#ownsCanvas) From 1151dd0b067532b01f286fa93473894b9d28115a Mon Sep 17 00:00:00 2001 From: Liam Egan Date: Tue, 25 Aug 2026 08:52:50 -0700 Subject: [PATCH 2/2] Removing resize listener --- .../wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts b/packages/wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts index bfa5fd8..6320c89 100644 --- a/packages/wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts +++ b/packages/wtc-gl/src/recipes/ScrollRenderer/ScrollRenderer.ts @@ -104,7 +104,6 @@ export class ScrollRenderer { this.#resizeObserver = new ResizeObserver(this.resize) this.#resizeObserver.observe(this.canvas) } - window.addEventListener('resize', this.resize) this.resize() } @@ -122,8 +121,8 @@ export class ScrollRenderer { * layout size), which also avoids the scrollbar-gutter offset that * `window.innerWidth/innerHeight` would introduce. * - * Called automatically on construction, whenever a canvas element's size - * changes, and on every `resize` event. + * Called automatically on construction, and when canvas resizeObsever triggers + * and at the start of every rendered frame. */ resize() { const canvas = this.canvas @@ -286,7 +285,7 @@ export class ScrollRenderer { } /** - * Stops the render loop, removes the resize listener, and destroys all + * Stops the render loop, disconnects the resize observer, and destroys all * registered scenes (disconnecting their `IntersectionObserver`s). * * When the renderer created its own canvas, the WebGL context is also @@ -306,7 +305,6 @@ export class ScrollRenderer { */ destroy(): HTMLCanvasElement { this.playing = false - window.removeEventListener('resize', this.resize) this.#resizeObserver?.disconnect() this.#resizeObserver = null this.#scenes.forEach((s) => s.destroy())