Can flexbox detect when a flex item wraps? No. In CSS, once the browser renders the page on the initial cascade, it doesn't reflow the document when an element wraps. As a result, parent elements don't know when their children wrap.
@ChrisCashwell Calculate width of each element, whilst looping, accumulate element widths, and when accumulated width exceeds the container width, you know that element has wrapped.
The CSS flex-wrap property is used to specify whether flex items are forced into a single line or wrapped onto multiple lines. The flex-wrap property allows enabling the control direction in which lines are stacked. It is used to designate a single line or multi-line format to flex items inside the flex container.
If you are using flexbox and want the content to wrap, you must specify flex-wrap: wrap . By default flex items don't wrap. To have the images be three-across, you should specify flex-basis: 33.33333% .
Here's one potential solution. There might be other gotchas and edge cases you need to check for.
The basic idea is to loop through the flex items
and test their top
position against the previous sibling. If the top
value is greater (hence further down the page) then the item has wrapped.
The function detectWrap
returns an array of DOM elements that have wrapped, and could be used to style as desired.
The function could ideally be used with a ResizeObserver
(while using window
's resize
event as a fallback) as a trigger to check for wrapping as the window is resized or as elements in the page change due to scripts and other user-interaction. Because the StackOverflow code window doesn't resize it won't work here.
Here's a CodePen that works with a screen resize.
var detectWrap = function(className) {
var wrappedItems = [];
var prevItem = {};
var currItem = {};
var items = document.getElementsByClassName(className);
for (var i = 0; i < items.length; i++) {
currItem = items[i].getBoundingClientRect();
if (prevItem && prevItem.top < currItem.top) {
wrappedItems.push(items[i]);
}
prevItem = currItem;
};
return wrappedItems;
}
window.onload = function(event){
var wrappedItems = detectWrap('item');
for (var k = 0; k < wrappedItems.length; k++) {
wrappedItems[k].className = "wrapped";
}
};
div {
display: flex;
flex-wrap: wrap;
}
div > div {
flex-grow: 1;
flex-shrink: 1;
justify-content: center;
background-color: #222222;
padding: 20px 0px;
color: #FFFFFF;
font-family: Arial;
min-width: 300px;
}
div.wrapped {
background-color: red;
}
<div>
<div class="item">A</div>
<div class="item">B</div>
<div class="item">C</div>
</div>
Little bit improved snippet on jQuery for this purpose.
wrapped();
$(window).resize(function() {
wrapped();
});
function wrapped() {
var offset_top_prev;
$('.flex-item').each(function() {
var offset_top = $(this).offset().top;
if (offset_top > offset_top_prev) {
$(this).addClass('wrapped');
} else if (offset_top == offset_top_prev) {
$(this).removeClass('wrapped');
}
offset_top_prev = offset_top;
});
}
I've modified sansSpoon's code to work even if the element isn't at the absolute top of the page. Codepen: https://codepen.io/tropix126/pen/poEwpVd
function detectWrap(node) {
for (const container of node) {
for (const child of container.children) {
if (child.offsetTop > container.offsetTop) {
child.classList.add("wrapped");
} else {
child.classList.remove("wrapped");
}
}
}
}
Note that margin-top
shouldn't be applied to items since it's factored into getBoundingClientRect
and will trigger the wrapped class to apply on all items.
I'm using a similar approach in determining if a <li>
has been wrapped in an <ul>
that has it's display set to flex
.
ul = document.querySelectorAll('.list');
function wrapped(ul) {
// loops over all found lists on the page - HTML Collection
for (var i=0; i<ul.length; i++) {
//Children gets all the list items as another HTML Collection
li = ul[i].children;
for (var j=0; j<li.length; j++) {
// offsetTop will get the vertical distance of the li from the ul.
// if > 0 it has been wrapped.
loc = li[j].offsetTop;
if (loc > 0) {
li[j].className = 'wrapped';
} else {
li[j].className = 'unwrapped';
}
}
}
}
I noticed elements will typically wrap in relation to the first element. Comparing offset top of each element to the first element is a simpler approach. This works for wrap and wrap-reverse. (Probably won't work if elements use flex order)
var wrappers = $('.flex[class*="flex-wrap"]'); //select flex wrap and wrap-reverse elements
if (wrappers.length) { //don't add listener if no flex elements
$(window)
.on('resize', function() {
wrappers.each(function() {
var prnt = $(this),
chldrn = prnt.children(':not(:first-child)'), //select flex items
frst = prnt.children().first();
chldrn.each(function(i, e) { $(e).toggleClass('flex-wrapped', $(e).offset().top != frst.offset().top); }); //element has wrapped
prnt.toggleClass('flex-wrapping', !!prnt.find('.flex-wrapped').length); //wrapping has started
frst.toggleClass('flex-wrapped', !!!chldrn.filter(':not(.flex-wrapped)').length); //all are wrapped
});
})
.trigger('resize'); //lazy way to initially call the above
}
.flex {
display: flex;
}
.flex.flex-wrap {
flex-wrap: wrap;
}
.flex.flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.flex.flex-1 > * { /*make items equal width*/
flex: 1;
}
.flex > * {
flex-grow: 1;
}
.cc-min-width-200 > * { /*child combinator*/
min-width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="flex flex-1 flex-wrap-reverse cc-min-width-200">
<div>Hello</div>
<div>There</div>
<div>World</div>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With