"Real-time Gantt charts on the web? Will performance be good?"
A question I received 2 years ago. Answer: "YES, but..."
Today, I'll share how we implemented a Gantt chart that renders 10,000 tasks at 60fps.
Technology Stack Selection: Why SVG?
We debated whether to draw with Canvas, SVG, or DOM.
Canvas is fast but interaction is difficult and accessibility is almost none.
DOM supports native events but takes 2 seconds even with just 1000 items.
SVG is in between. Fast at around 200ms while supporting native events.
SVG is optimal for Gantt charts.
SVG Optimization Strategy
But SVG is also slow if used as-is. Optimization is key.
class OptimizedSVGGantt {
private taskPool: Map<string, SVGRectElement> = new Map();
// 1. Minimize DOM manipulation with Object Pooling
getTaskBar(taskId: string): SVGRectElement {
if (this.taskPool.has(taskId)) {
return this.taskPool.get(taskId)!;
}
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
this.taskPool.set(taskId, rect);
return rect;
}
// 2. Use CSS Transform (GPU acceleration)
moveTask(element: SVGElement, x: number, y: number) {
element.style.transform = `translate(${x}px, ${y}px)`;
element.style.willChange = 'transform';
}
}
Virtual Scrolling: Handling 10,000 Tasks
Rendering all 10,000 tasks exceeds 20MB memory.
Solution? Only render what's visible on screen.
class VirtualGantt {
private itemHeight = 30;
private buffer = 5;
calculateVisibleRange(scrollTop: number, viewportHeight: number, totalTasks: number): [number, number] {
const startIndex = Math.floor(scrollTop / this.itemHeight);
const endIndex = Math.ceil((scrollTop + viewportHeight) / this.itemHeight);
// Add buffer (smooth scrolling)
const bufferedStart = Math.max(0, startIndex - this.buffer);
const bufferedEnd = Math.min(totalTasks, endIndex + this.buffer);
return [bufferedStart, bufferedEnd];
}
}
Only 40 of 10,000 are actually rendered.
Memory usage drops 95%.
WebSocket + CRDT: Real-Time Synchronization
What happens when multiple people modify the same task simultaneously?
CRDT (Conflict-free Replicated Data Type) is the answer.
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
class RealtimeGanttSync {
private yDoc: Y.Doc;
private yTasks: Y.Map<any>;
initialize() {
this.yDoc = new Y.Doc();
this.yTasks = this.yDoc.getMap('tasks');
// WebSocket connection
new WebsocketProvider('wss://api.plexo.work/gantt', 'project-123', this.yDoc);
// Detect changes
this.yTasks.observe(this.handleRemoteChange);
}
// Auto-merge without conflicts!
handleRemoteChange = (event: Y.YMapEvent<any>) => {
event.changes.keys.forEach((change, key) => {
if (change.action === 'update') {
const task = this.yTasks.get(key);
this.updateGanttUI(key, task);
this.showNotification(`${task.modifiedBy} modified`);
}
});
};
}
Yjs automatically resolves conflicts.
A changes start date and B changes end date, no problem.
Performance Measurement Results
For 10,000-task project:
- Initial rendering: 1.2 seconds
- Scroll FPS: 55-60fps
- Real-time update delay: 50ms
- Memory usage: 50MB
Before optimization, even 1000 took 10 seconds.
Now tested up to 50,000.
Practical Implementation Tips
Profiling is the Answer
class PerformanceMonitor {
measure(name, fn) {
performance.mark(`${name}-start`);
const result = fn();
performance.mark(`${name}-end`);
performance.measure(name, `${name}-start`, `${name}-end`);
const measure = performance.getEntriesByName(name)[0];
console.log(`${name}: ${measure.duration}ms`);
return result;
}
}
Open Chrome DevTools Performance tab and measure.
You can know exactly where the bottleneck is.
Gradual Improvement
No need to be perfect from the start.
Step 1: Implement basic features (slow is OK)
Step 2: Apply Virtual Scrolling
Step 3: Separate heavy calculations with WebWorker
Step 4: Introduce caching strategy
Step 5: Rewrite core algorithms with WebAssembly
User Experience First
Even if slow, response must be immediate.
async handleUserAction(action) {
// Immediate feedback
this.showLoadingIndicator();
// Heavy work asynchronously
await this.processInBackground(action);
// Update after completion
this.hideLoadingIndicator();
}
Conclusion: Technology is Just a Means
No matter how technically perfect, if users don't use it, it's meaningless.
What we focused on:
- Fast Response: Must move immediately on click
- Smooth Scrolling: Won't use if laggy
- Real-Time Sync: Collaboration is key
- Intuitive Interface: OK without manual
The most rewarding moment was when a user said "Wow, this is really fast!"
Planning to build web-based Gantt charts?
Hope this article helps.
Experience high-performance real-time Gantt charts. Plexo

Top comments (0)