Virtual Scrolling Techniques and Performance Optimization for Large Document Editors
This article explains how virtual scrolling can dramatically improve the performance of large online document editors by rendering only visible content, discusses various implementation strategies such as progressive pagination, canvas rendering, line‑ and block‑level virtualization, and details state management, view‑locking, fast‑scroll handling, and integration scenarios like anchor navigation, find‑replace, and comment systems, while providing code examples and performance test results.
Overview
Virtual scrolling is a technique that optimizes long‑list performance by rendering only the items visible in the viewport and using placeholders for off‑screen content, thereby reducing DOM operations, memory usage, and rendering time.
Problem Description
Users reported severe lag when editing large documents with many tables, with LCP reaching 6896 ms, FCP 4777 ms, and TTI over 13 s. The editor rendered the entire document, causing excessive DOM nodes and low FPS.
Solution Research
Four main approaches were examined:
Progressive pagination loading (e.g., Notion‑style data‑driven chunks, SSE support).
Canvas‑based pagination rendering (e.g., Google Docs, Tencent Docs).
Line‑level virtual scrolling (render only visible lines).
Block‑level virtual scrolling (render only visible blocks, useful for complex structures like code blocks and tables).
Implementation Details
The core model uses three states for each node: loading , viewport , and placeholder . Placeholders occupy space without rendering full content, while the viewport state renders the actual block.
type NodeState = {
mode: "loading" | "placeholder" | "viewport";
height: number;
};Scroll handling can be based on the Scroll Event or the IntersectionObserver API . The latter observes placeholder elements and switches them to viewport when they intersect the viewport.
const onIntersect = useMemoizedFn((entries) => {
entries.forEach(entry => {
const node = ELEMENT_TO_NODE.get(entry.target);
if (!node) return;
const rect = entry.boundingClientRect;
if (entry.isIntersecting) {
node.changeStatus("viewport", rect.height);
} else if (node.state.mode !== "loading") {
node.changeStatus("placeholder", rect.height);
}
});
});Viewport locking ensures that when a block changes height (e.g., after loading), the scroll position is adjusted to prevent visual jumps. The layout module disables overflow‑anchor and sets history.scrollRestoration to manual for precise control.
class LayoutModule {
initLayoutModule() {
const dom = this.scroll instanceof Window ? document.body : this.scroll;
dom.style.overflowAnchor = "none";
if (history.scrollRestoration) history.scrollRestoration = "manual";
}
}Fast scrolling is handled by pre‑rendering a buffer (typically half the viewport height) and using throttled scroll listeners to render additional blocks when the scroll distance exceeds a multiple of the viewport size.
Scenario Integration
Various application scenarios were adapted:
Anchor navigation : On page load, the target block is forced to viewport state before scrolling to its position, with view‑lock adjustments.
Find & replace : Search results trigger forceRenderBlock for the containing block, then scroll to the exact text range.
Inline comments : Comment overlays are rendered only when their associated block enters the viewport; position updates are propagated to subsequent blocks based on index.
Performance Evaluation
Metrics used were LCP and TTI. Tests on a real‑world large document showed:
Editor render time reduced from 2505 ms to 446 ms (82% improvement).
LCP dropped from 6896 ms to 3376 ms (51% improvement).
TTI decreased from 13343 ms to 3878 ms (71% improvement).
Additional synthetic benchmarks demonstrated varying gains depending on block type:
Pure text (1 M characters): slight slowdown due to extra modules.
Simple blocks (e.g., code snippets): 66% faster render.
Table blocks (100 tables × 4 cells): 87% faster render and 79% faster TTI.
Conclusion
Virtual scrolling, combined with careful state management, view‑locking, and scenario‑specific hooks, provides substantial performance benefits for large‑scale online document editors while maintaining correct selection, comment, and navigation behavior.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.