Measuring a Horizontal Flex List: Calculating Dynamic Overflows in JavaScript
In modern UI design, horizontal lists of elements (such as filter badges, tags, or tab menus) are commonplace. To prevent them from wrapping to a new line and cluttering the interface, we often want to display them in a single row.
But what happens when the screen or container shrinks? If we simply hide the overflow with overflow: hidden, the items will be cut off mid-word, leaving an unpolished user experience.
A better solution is to measure the elements dynamically and display a "+N More" button representing the hidden items. Today, we will discuss how to write a robust JavaScript layout algorithm to determine exactly how many items fit and how to handle the "+N More" button dimension footprint.
The Challenge
Every element in our list contains different text, meaning their widths are dynamic and unknown at design time. We cannot rely on simple division.
Instead, our algorithm must:
- Loop through the list items and measure their individual bounding boxes.
- Accumulate their widths along with the gaps between them.
- Compare the accumulated width to the parent container's width.
- If the total exceeds the parent, calculate how many items can be rendered after allocating space for a "Show More" button.
The Calculation Algorithm
Let's break down the logic. We will assume our "Show More" button has a fixed width of 75px and the gap between items is 8px.
Step 1: Check If Everything Fits
First, we run a check to see if all items fit in the container without modifying anything.
const gap = 8;
const parentWidth = container.clientWidth;
let accumulatedWidth = 0;
let allFit = true;
for (let i = 0; i < items.length; i++) {
const itemWidth = items[i].getBoundingClientRect().width;
accumulatedWidth += itemWidth + (i > 0 ? gap : 0);
if (accumulatedWidth > parentWidth) {
allFit = false;
break;
}
}
Step 2: Handle the "Show More" Overflow
If the items do not all fit, we must insert a "Show More" button. Because this button takes up space (75px), the available width for rendering items is reduced:
$$\text{Available Width} = \text{Parent Width} - \text{Button Width} - \text{Gap}$$
We must recalculate how many items fit within this reduced space:
const showMoreWidth = 75;
const availableWidth = parentWidth - showMoreWidth - gap;
let currentWidth = 0;
let fitCount = 0;
for (let i = 0; i < items.length; i++) {
const itemWidth = items[i].getBoundingClientRect().width;
const spaceNeeded = itemWidth + (i > 0 ? gap : 0);
if (currentWidth + spaceNeeded > availableWidth) {
break; // This item (and subsequent ones) will overflow
}
currentWidth += spaceNeeded;
fitCount++;
}
Step 3: Toggle Visibility and Insert the Button
Once we know fitCount, we hide any items starting from index fitCount onwards and append our button containing the count of hidden items: items.length - fitCount.
Live Interactive Demo
Resize the box below by dragging the handle in the bottom-right corner. The layout engine will recalculate the fit metrics in real time and display a "+N MORE" button if the items overflow:
* Drag bottom-right corner to expand horizontally
Conclusion
Using JavaScript's getBoundingClientRect() in combination with a ResizeObserver allows us to build highly responsive, context-aware navigation elements. Instead of building messy, wrap-around layouts, this approach guarantees that elements fit clean grid alignments perfectly!