Solving Mobile Scroll Jank with Framework7 Virtual Lists
Stop mobile app stuttering by replacing naive loops with Framework7 Virtual Lists. Learn how to render thousands of items without bloating the DOM.
19 Jul 2025, 06:58 UTC

The DOM Bloat Problem
When building hybrid mobile apps, the instinct is to loop through a data array and render a template for every item. While this works for a dozen entries, it fails quickly once you hit hundreds or thousands. On mobile devices, rendering 1,000 complex list items creates a massive Document Object Model (DOM) tree. This leads to "jank"—stuttering animations and sluggish scrolling—because the browser must track the position and style of every single element, even those far off-screen.
The solution is Virtualization. Instead of rendering the entire dataset, you render only the items currently visible in the viewport, plus a small buffer. As the user scrolls, Framework7 recycles the existing DOM nodes, swapping the content inside them rather than destroying and recreating elements.
How Virtual List Works
Framework7's Virtual List operates by calculating the total scrollable height based on the number of items and their height. It then positions a small window of active elements using absolute positioning or transforms to mimic a continuous list.
This approach keeps the memory footprint constant regardless of whether your list has 100 items or 100,000. The main thread remains responsive because the browser only manages a handful of active DOM nodes at any given time.
Implementation Example
To implement a virtual list, you need a container element, a data source, and a render function. This example assumes you are using Framework7 v6+ in a standard JavaScript environment.
// 1. Define your data source (e.g., 5,000 records)const items = Array.from({ length: 5000 }, (_, i) => ({id: i,title: `Item ${i + 1}`,description: `Details for record ${i + 1}`}));// 2. Initialize the Virtual List on a specific DOM elementvar vl = f7.virtualList.create({el: '.virtual-list-container', // The target elementdata: items, // Your array of objectsitemHeight: 70, // Fixed height of each row in pixelsrenderItem: function (item, index) {return `<div class="item-content"><div class="item-inner><div class="item-title">${item.title}</div><div class="item-after">${item.description}</div></div></div>`;},});
Execution Details
- Where to run: This code should execute within your page's
onPageInitoronPageBeforeInitevent to ensure the DOM element exists. - Permissions: Standard client-side JS permissions; no special system privileges required.
- Expected Check: Open Chrome DevTools, inspect the
.virtual-list-container, and scroll rapidly. You should see only a small number of.item-contentdivs being updated, rather than thousands of elements appearing in the DOM.
The Trade-off: Fixed vs. Variable Heights
The primary engineering trade-off with Virtual Lists is the requirement for predictable item heights. The itemHeight property is used to calculate the total scrollbar length. If your items have variable heights (e.g., some have three lines of text and others have one), the scroll position can "drift," causing the list to jump or stutter as the engine corrects the offset.
While Framework7 allows for some flexibility, variable heights increase complexity. You may need to implement a measurement pass or provide a function to calculate height dynamically, which can degrade performance if not handled carefully.
Critical Limitations
- Memory Heap: Virtualization solves DOM bloat, but not JS heap bloat. If your 10,000-item array contains massive objects, the browser may still crash. For truly massive datasets, combine Virtual List with server-side pagination or a local database (like SQLite).
- State Management: Because DOM nodes are recycled, you cannot rely on the DOM to hold state (like a checked checkbox). State must be stored in your
dataarray and reapplied during therenderItemcall. - Lifecycle Hooks: If you are using a component-based framework (like Vue or React) with Framework7, remember that the
renderItemfunction returns a string or element; it does not necessarily trigger the full lifecycle hooks of a child component every time a row is recycled.
Verification and Rollback
To verify the implementation, use the browser's Element inspector. If the number of child elements in your list container stays roughly the same while you scroll through thousands of items, the virtualization is working. If the node count grows linearly with your scroll, the list is rendering naively.
Rollback: Since this operation modifies the DOM via JavaScript, you can revert to a standard v-for or map() loop by removing the virtualList.create call and replacing the container with a standard HTML loop. No permanent state changes are made to the server or database.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.