refactor: split start() into size-lock, read, write phases
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user