perf(timeline): add horizontal virtualization for allocation bars
Tracks scroll position via requestAnimationFrame to avoid re-renders on every pixel. Allocation bars outside the visible horizontal window (+ 10-column overscan) are skipped during render, reducing DOM nodes significantly at day zoom (365 days × 40px = 14,600px canvas). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,9 @@ interface TimelineResourcePanelProps {
|
||||
optimisticAllocations: TimelineVisualOverrides;
|
||||
suppressHoverInteractions: boolean;
|
||||
onInlineEdit?: (allocationId: string, initialValues: { startDate: Date; endDate: Date; hoursPerDay: number }, barRect: DOMRect) => void;
|
||||
/** Horizontal virtualization: current scroll offset and visible container width */
|
||||
scrollLeft?: number;
|
||||
containerWidth?: number;
|
||||
// Layout from useTimelineLayout
|
||||
CELL_WIDTH: number;
|
||||
dates: Date[];
|
||||
@@ -130,6 +133,8 @@ function TimelineResourcePanelInner({
|
||||
optimisticAllocations,
|
||||
suppressHoverInteractions,
|
||||
onInlineEdit,
|
||||
scrollLeft = 0,
|
||||
containerWidth = 1200,
|
||||
CELL_WIDTH,
|
||||
dates,
|
||||
totalCanvasWidth,
|
||||
@@ -513,6 +518,8 @@ function TimelineResourcePanelInner({
|
||||
multiSelectState,
|
||||
suppressHoverInteractions,
|
||||
onInlineEdit,
|
||||
scrollLeft,
|
||||
containerWidth,
|
||||
)}
|
||||
{filters.showVacations &&
|
||||
renderVacationBlocks(
|
||||
@@ -619,7 +626,12 @@ function renderAllocBlocksFromData(
|
||||
multiSelectState: MultiSelectState,
|
||||
suppressHoverInteractions: boolean,
|
||||
onInlineEdit?: (allocationId: string, initialValues: { startDate: Date; endDate: Date; hoursPerDay: number }, barRect: DOMRect) => void,
|
||||
scrollLeft = 0,
|
||||
containerWidth = 1200,
|
||||
) {
|
||||
const OVERSCAN_PX = 10 * CELL_WIDTH;
|
||||
const visibleLeft = scrollLeft - OVERSCAN_PX;
|
||||
const visibleRight = scrollLeft + containerWidth + OVERSCAN_PX;
|
||||
const anyDragActive = dragState.isDragging || allocDragState.isActive;
|
||||
const selectedAllocationSet = new Set(multiSelectState.selectedAllocationIds);
|
||||
|
||||
@@ -764,6 +776,12 @@ function renderAllocBlocksFromData(
|
||||
return [];
|
||||
}
|
||||
|
||||
// Horizontal virtualization: skip bars entirely outside the visible window
|
||||
const effectiveRight = segmentLeft + segmentWidth;
|
||||
if (effectiveRight < visibleLeft || segmentLeft > visibleRight) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const handleWidth = segmentWidth >= 48 ? 10 : 6;
|
||||
const dragInset = Math.min(handleWidth, Math.max(2, Math.floor(segmentWidth / 4)));
|
||||
const segmentInfo: AllocMouseDownInfo = {
|
||||
|
||||
Reference in New Issue
Block a user