fix scroll smooth
This commit is contained in:
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user