Bolt: Optimized GradientBlinds resize performance #50

Closed
ragusa-it wants to merge 1 commits from bolt-performance-gradient-blinds-resize-5528101139476827014 into main

View File

@@ -280,18 +280,36 @@ void main() {
const mesh = new Mesh(gl, { geometry, program });
meshRef.current = mesh;
const resize = () => {
const resize = (entries?: ResizeObserverEntry[]) => {
let width, height;
if (entries && entries.length > 0) {
const entry = entries[0];
width = entry.contentRect.width;
chatgpt-codex-connector[bot] commented 2026-02-03 01:49:51 +00:00 (Migrated from github.com)
Review

P1 Badge Add explicit numeric type for width/height

With strict: true in tsconfig.json (which enables noImplicitAny), let width, height; is an implicit any and will fail pnpm exec tsc --noEmit (or any strict type check). This is a build-breaking regression introduced by the change; please add an explicit number type or initialize the variables (e.g., let width = 0, height = 0) so the file type-checks again.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Add explicit numeric type for width/height** With `strict: true` in `tsconfig.json` (which enables `noImplicitAny`), `let width, height;` is an implicit `any` and will fail `pnpm exec tsc --noEmit` (or any strict type check). This is a build-breaking regression introduced by the change; please add an explicit `number` type or initialize the variables (e.g., `let width = 0, height = 0`) so the file type-checks again. Useful? React with 👍 / 👎.
height = entry.contentRect.height;
// Optimization: Defer calling getBoundingClientRect (which forces layout)
// to the next frame, since ResizeObserver runs after layout but before paint.
requestAnimationFrame(() => {
if (rendererRef.current) {
rectRef.current = container.getBoundingClientRect();
}
});
} else {
const rect = container.getBoundingClientRect();
copilot-pull-request-reviewer[bot] commented 2026-02-03 01:50:27 +00:00 (Migrated from github.com)
Review

The requestAnimationFrame callback could execute after the component has unmounted. While there is a check for rendererRef.current, this doesn't prevent the container.getBoundingClientRect() call if the component unmounts between the check and the call. This could lead to accessing a detached DOM element.

Add a cleanup mechanism to cancel pending requestAnimationFrame callbacks, or store the RAF ID and cancel it in the useEffect cleanup function. Alternatively, add an additional check that the container is still in the document before calling getBoundingClientRect().

          // Ensure renderer is still active and container is still in the document
          if (
            !rendererRef.current ||
            typeof document === 'undefined' ||
            !container ||
            !(container as HTMLElement).isConnected ||
            !document.body.contains(container)
          ) {
            return;
          }

          rectRef.current = container.getBoundingClientRect();
The requestAnimationFrame callback could execute after the component has unmounted. While there is a check for rendererRef.current, this doesn't prevent the container.getBoundingClientRect() call if the component unmounts between the check and the call. This could lead to accessing a detached DOM element. Add a cleanup mechanism to cancel pending requestAnimationFrame callbacks, or store the RAF ID and cancel it in the useEffect cleanup function. Alternatively, add an additional check that the container is still in the document before calling getBoundingClientRect(). ```suggestion // Ensure renderer is still active and container is still in the document if ( !rendererRef.current || typeof document === 'undefined' || !container || !(container as HTMLElement).isConnected || !document.body.contains(container) ) { return; } rectRef.current = container.getBoundingClientRect(); ```
rectRef.current = rect;
copilot-pull-request-reviewer[bot] commented 2026-02-03 01:50:27 +00:00 (Migrated from github.com)
Review

The deferred update of rectRef.current can cause mouse position calculations to use stale position data. When a resize occurs, rectRef.current won't be updated until the next animation frame, but mouse events (via onPointerMove on lines 333-358 and the loop function on lines 360-409) may use the outdated rect during this gap. This can lead to incorrect mouse coordinate calculations, particularly noticeable during rapid resize operations.

Consider updating rectRef.current synchronously using contentRect properties (left, top can be derived from container.getBoundingClientRect() on initial call), or accept that mouse position calculations may be slightly off during resize until the next frame.

        // Use a synchronous rect update so mouse position calculations
        // always see up-to-date geometry during resize.
        const rect = container.getBoundingClientRect();
        rectRef.current = rect;
        width = rect.width;
        height = rect.height;
The deferred update of rectRef.current can cause mouse position calculations to use stale position data. When a resize occurs, rectRef.current won't be updated until the next animation frame, but mouse events (via onPointerMove on lines 333-358 and the loop function on lines 360-409) may use the outdated rect during this gap. This can lead to incorrect mouse coordinate calculations, particularly noticeable during rapid resize operations. Consider updating rectRef.current synchronously using contentRect properties (left, top can be derived from container.getBoundingClientRect() on initial call), or accept that mouse position calculations may be slightly off during resize until the next frame. ```suggestion // Use a synchronous rect update so mouse position calculations // always see up-to-date geometry during resize. const rect = container.getBoundingClientRect(); rectRef.current = rect; width = rect.width; height = rect.height; ```
width = rect.width;
height = rect.height;
}
if (typeof window !== 'undefined') {
isMobileRef.current = window.innerWidth <= 768;
scrollPosRef.current = { x: window.scrollX, y: window.scrollY };
}
renderer.setSize(rect.width, rect.height);
renderer.setSize(width, height);
uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];
if (blindMinWidth && blindMinWidth > 0) {
const maxByMinWidth = Math.max(1, Math.floor(rect.width / blindMinWidth));
const maxByMinWidth = Math.max(1, Math.floor(width / blindMinWidth));
const effective = blindCount ? Math.min(blindCount, maxByMinWidth) : maxByMinWidth;
uniforms.uBlindCount.value = Math.max(1, effective);