fix scroll smooth

This commit is contained in:
2026-06-04 14:51:13 +05:30
parent 123092f4b8
commit b2d64bd335
15 changed files with 331 additions and 167 deletions

View File

@@ -36,6 +36,79 @@ const WORKFLOW_STEPS = [
{ label: "Monitor", icon: "📊", activateAt: 5 },
];
/**
* Bottom "Live Analytics" ticker. Self-contained: it owns its five fluctuation
* timers and the state they mutate, so a tick re-renders only this ~10-node
* subtree instead of the entire (canvas + overlay) OptimizationSection — which
* previously re-reconciled on every timer fire, competing with scroll updates.
*/
const LiveInsightBar = React.memo(function LiveInsightBar() {
const [orders, setOrders] = useState(59);
const [accuracy, setAccuracy] = useState(98.7);
const [activeVehicles, setActiveVehicles] = useState(5);
const [carbon, setCarbon] = useState(-12.0);
const [routeHealth, setRouteHealth] = useState(99.4);
useEffect(() => {
// Pause every ticker while the tab is hidden — no point re-rendering offscreen.
const guard = (fn: () => void) => () => {
if (!document.hidden) fn();
};
const ordersInterval = setInterval(guard(() => {
setOrders((prev) => prev + (Math.random() > 0.4 ? 1 : 0));
}), 4500);
const accuracyInterval = setInterval(guard(() => {
setAccuracy((prev) => {
const next = prev + (Math.random() - 0.5) * 0.15;
return parseFloat(Math.min(99.1, Math.max(98.4, next)).toFixed(2));
});
}), 2800);
const vehicleInterval = setInterval(guard(() => {
setActiveVehicles((prev) => (prev === 5 ? (Math.random() > 0.5 ? 4 : 5) : (Math.random() > 0.3 ? 5 : 4)));
}), 3500);
const carbonInterval = setInterval(guard(() => {
setCarbon((prev) => {
const next = prev + (Math.random() - 0.5) * 0.2;
return parseFloat(Math.min(-11.5, Math.max(-12.8, next)).toFixed(1));
});
}), 3200);
const healthInterval = setInterval(guard(() => {
setRouteHealth((prev) => {
const next = prev + (Math.random() - 0.5) * 0.12;
return parseFloat(Math.min(99.9, Math.max(98.8, next)).toFixed(2));
});
}), 2500);
return () => {
clearInterval(ordersInterval);
clearInterval(accuracyInterval);
clearInterval(vehicleInterval);
clearInterval(carbonInterval);
clearInterval(healthInterval);
};
}, []);
return (
<motion.div
className="dm-opt-insight"
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.3 }}
>
<span className="dm-opt-insight__dot" />
<span className="dm-opt-insight__text">Live Analytics: <strong>{orders} Orders</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">AI Accuracy: <strong>{accuracy}%</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Fleet: <strong>{activeVehicles}/5 EV Active</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Route Health: <strong>{routeHealth}%</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Carbon: <strong>{carbon}%</strong></span>
</motion.div>
);
});
export default function OptimizationSection() {
const containerRef = useRef<HTMLDivElement>(null);
const progressRef = useRef(0);
@@ -48,55 +121,6 @@ export default function OptimizationSection() {
const [isMobile, setIsMobile] = useState(false);
const [reduced, setReduced] = useState(false);
const [orders, setOrders] = useState(59);
const [accuracy, setAccuracy] = useState(98.7);
const [activeVehicles, setActiveVehicles] = useState(5);
const [carbon, setCarbon] = useState(-12.0);
const [routeHealth, setRouteHealth] = useState(99.4);
// Interval timers for high-fidelity live dashboard fluctuations
useEffect(() => {
const ordersInterval = setInterval(() => {
setOrders((prev) => prev + (Math.random() > 0.4 ? 1 : 0));
}, 4500);
const accuracyInterval = setInterval(() => {
setAccuracy((prev) => {
const delta = (Math.random() - 0.5) * 0.15;
const next = prev + delta;
return parseFloat(Math.min(99.1, Math.max(98.4, next)).toFixed(2));
});
}, 2800);
const vehicleInterval = setInterval(() => {
setActiveVehicles((prev) => (prev === 5 ? (Math.random() > 0.5 ? 4 : 5) : (Math.random() > 0.3 ? 5 : 4)));
}, 3500);
const carbonInterval = setInterval(() => {
setCarbon((prev) => {
const delta = (Math.random() - 0.5) * 0.2;
const next = prev + delta;
return parseFloat(Math.min(-11.5, Math.max(-12.8, next)).toFixed(1));
});
}, 3200);
const healthInterval = setInterval(() => {
setRouteHealth((prev) => {
const delta = (Math.random() - 0.5) * 0.12;
const next = prev + delta;
return parseFloat(Math.min(99.9, Math.max(98.8, next)).toFixed(2));
});
}, 2500);
return () => {
clearInterval(ordersInterval);
clearInterval(accuracyInterval);
clearInterval(vehicleInterval);
clearInterval(carbonInterval);
clearInterval(healthInterval);
};
}, []);
// Environment detection (client only).
useEffect(() => {
const mqMobile = window.matchMedia("(max-width: 767px)");
@@ -184,7 +208,7 @@ export default function OptimizationSection() {
},
});
const refresh = setTimeout(() => ScrollTrigger.refresh(), 300);
const refresh = setTimeout(() => ScrollTrigger.refresh(), 120);
return () => {
clearTimeout(refresh);
st.kill();
@@ -222,7 +246,11 @@ export default function OptimizationSection() {
progress={progressRef}
reduced={reduced}
isMobile={isMobile}
active={sceneActive}
// Only run the render loop while the section is actually pinned
// (filling the viewport). At a workflow seam two sections can both
// satisfy their activeIo margin; without the pin gate their two
// full-screen Bloom passes would run at once and drop FPS.
active={sceneActive && pinState === "pinned"}
/>
</div>
)}
@@ -382,25 +410,9 @@ export default function OptimizationSection() {
{/* KPI metrics */}
<div className="dm-opt-foot">
<MetricsPanel scroll={scroll} />
{/* Bottom insight bar */}
<motion.div
className="dm-opt-insight"
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.3 }}
>
<span className="dm-opt-insight__dot" />
<span className="dm-opt-insight__text">Live Analytics: <strong>{orders} Orders</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">AI Accuracy: <strong>{accuracy}%</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Fleet: <strong>{activeVehicles}/5 EV Active</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Route Health: <strong>{routeHealth}%</strong></span>
<span className="dm-opt-insight__sep" />
<span className="dm-opt-insight__text">Carbon: <strong>{carbon}%</strong></span>
</motion.div>
{/* Bottom insight bar — isolated so its live tickers don't re-render
the whole section (and never fight the scroll handler). */}
<LiveInsightBar />
</div>
</div>
</div>
@@ -427,6 +439,11 @@ const styles = `
height: 100vh;
overflow: hidden;
background: transparent;
/* Promote the pinned layer to its own GPU compositing layer so scroll-driven
pin/scrub updates never trigger main-thread paints of the rest of the page. */
will-change: transform;
transform: translateZ(0);
backface-visibility: hidden;
}
.dm-opt.is-pinned .dm-opt-sticky { position: fixed; top: 0; left: 0; }
.dm-opt.is-after .dm-opt-sticky { position: absolute; top: auto; bottom: 0; }