refactor: split start() into size-lock, read, write phases

This commit is contained in:
2026-04-24 13:22:45 +02:00
parent 415dcb8459
commit 2ba0b90fce

View File

@@ -24,70 +24,68 @@ export class GravityEngine {
start() { start() {
if (this.isRunning) return; if (this.isRunning) return;
this.isRunning = true; this.isRunning = true;
this.dirtyElements = [];
const containers = document.querySelectorAll(".gravity-body"); const containers = document.querySelectorAll(".gravity-body");
const targetElements: HTMLElement[] = []; const leafElements: HTMLElement[] = [];
// Helper to recursively drill down to leaf nodes // Phase 1 — Size-lock: freeze intermediate container dimensions
// so they don't collapse when children become position:fixed.
const extractLeaves = (el: HTMLElement) => { const extractLeaves = (el: HTMLElement) => {
// 1. Define "solid" elements that should fall as one single piece
const isSolid = ["SVG", "IMG", "BUTTON", "IFRAME", "A"].includes( const isSolid = ["SVG", "IMG", "BUTTON", "IFRAME", "A"].includes(
el.tagName.toUpperCase(), el.tagName.toUpperCase(),
); );
// 2. Base case: If it's solid, or has no children, it's a target
if (isSolid || el.children.length === 0) { if (isSolid || el.children.length === 0) {
// Only extract elements that actually take up visual space
if (el.offsetWidth > 0 && el.offsetHeight > 0) { if (el.offsetWidth > 0 && el.offsetHeight > 0) {
targetElements.push(el); leafElements.push(el);
} }
return; return;
} }
el.style.width = `${el.offsetWidth}px`; el.style.width = `${el.offsetWidth}px`;
el.style.height = `${el.offsetHeight}px`; el.style.height = `${el.offsetHeight}px`;
this.dirtyElements.push(el);
Array.from(el.children).forEach((child) => { Array.from(el.children).forEach((child) =>
extractLeaves(child as HTMLElement); extractLeaves(child as HTMLElement),
}); );
}; };
containers.forEach((container) => { containers.forEach((container) => {
const htmlContainer = container as HTMLElement; const htmlContainer = container as HTMLElement;
// Lock the main container's dimensions
htmlContainer.style.width = `${htmlContainer.offsetWidth}px`; htmlContainer.style.width = `${htmlContainer.offsetWidth}px`;
htmlContainer.style.height = `${htmlContainer.offsetHeight}px`; htmlContainer.style.height = `${htmlContainer.offsetHeight}px`;
this.dirtyElements.push(htmlContainer);
// Start the recursive extraction on its direct children Array.from(htmlContainer.children).forEach((child) =>
Array.from(htmlContainer.children).forEach((child) => { extractLeaves(child as HTMLElement),
extractLeaves(child as HTMLElement); );
});
}); });
const initialStates = targetElements.map((el) => { // Phase 2 — Read: batch all getBoundingClientRect() before any writes.
const rect = el.getBoundingClientRect(); const snapshots = leafElements.map((el) => ({
return { el, rect }; el,
}); rect: el.getBoundingClientRect(),
}));
this.bodies = initialStates.map(({ el, rect }) => { // Phase 3 — Write: apply fixed positioning using snapshotted rects.
// Lock dimensions so it doesn't warp when pulled out of flex/grid this.bodies = snapshots.map(({ el, rect }) => {
el.style.width = `${rect.width}px`; el.style.width = `${rect.width}px`;
el.style.height = `${rect.height}px`; el.style.height = `${rect.height}px`;
el.style.margin = "0px"; el.style.margin = "0px";
// Snap to fixed positioning at its exact current visual location
el.style.position = "fixed"; el.style.position = "fixed";
el.style.left = "0px"; el.style.left = "0px";
el.style.top = "0px"; el.style.top = "0px";
el.style.transform = `translate(${rect.left}px, ${rect.top}px)`; el.style.transform = `translate(${rect.left}px, ${rect.top}px)`;
this.dirtyElements.push(el);
const body: PhysicsBody = { const body: PhysicsBody = {
el: el, el,
x: rect.left, x: rect.left,
y: rect.top, y: rect.top,
vx: (Math.random() - 0.5) * 8, // slight explosion outward vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 5, // slight pop upward vy: (Math.random() - 0.5) * 5,
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
originX: rect.left, originX: rect.left,