Implementing a Virtual Waterfall List in Vue 3 + TypeScript with Dynamic Height Support
This article provides a step‑by‑step tutorial on building a virtual waterfall (masonry) list component in Vue 3 using TypeScript, covering component structure, props, state management, rendering logic, handling variable item heights via temporary DOM measurement, performance optimizations, and practical usage examples.
Overview
The article explains how to create a virtual waterfall (masonry) list component in Vue 3 with TypeScript, aiming to reproduce the scrolling performance of platforms like Xiaohongshu while supporting both fixed‑height and dynamic‑height items.
Component Structure and Styles
The DOM consists of three layers: <div class="fs-virtual-waterfall-container"> (scroll container), <div class="fs-virtual-waterfall-list"> (relative list), and <div class="fs-virtual-waterfall-item"> (absolutely positioned items). Styles are defined using SCSS to set container size, overflow, and item positioning.
Props and Initial State
Props include gap , column , pageSize , optional enterSize , and a request function for data fetching. Reactive state tracks loading status, pagination, the raw data list, scroll viewport dimensions, and two queue structures: one for column layout ( queueState ) and one for the flattened render list ( renderList ).
Core Algorithms
1. Column Height Calculation : A computed property computedHeight determines the minimum and maximum column heights, which are used to place new items in the shortest column.
2. Item Generation : generatorItem creates an IRenderItem containing the original data, calculated y offset, height, and inline style (width, height, transform).
3. Render List Filtering : renderList filters the flattened cardList to keep only items whose y + h overlaps the current scroll window ( start ‑ end ).
Dynamic Height Handling
For items whose height cannot be known beforehand (e.g., text‑only cards), a temporary DOM container ( #temporary-list ) is used. Items are first rendered invisibly, their real heights are measured with getBoundingClientRect() , and the results are stored in itemSizeInfo . After measurement, the temporary container is hidden and the normal virtual list is updated.
Data Loading and Scroll Handling
The component initializes by fetching the first page, calculating item widths, and mounting the temporary list for height measurement. On scroll, handleScroll updates start and, if the scroll reaches the minimum column height, either loads more data (when the queue is exhausted) or adds a configurable number of items ( enterSize ) from the already‑fetched data.
Responsive Re‑calculation
A resize observer triggers reComputedQueue , which recomputes column widths, clears the queue, and re‑mounts a batch of items (default pageSize ) to avoid full re‑measurement of thousands of elements.
Performance Optimizations
The article proposes a “batch‑enter” strategy: instead of re‑measuring the entire data set on every resize or scroll, only a small batch (e.g., enterSize or column * 2 ) is processed, dramatically reducing DOM operations and keeping scrolling smooth even with large data volumes.
Usage Example
A parent component demonstrates how to import FsVirtualWaterfall , provide a mock getData promise, and supply a slot template for rendering each card, including optional animation styles.
Conclusion
The final component achieves a performant virtual waterfall list with support for variable item heights, lazy loading, responsive layout, and extensible animation, and the source code is linked to a GitHub repository.
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.