How to detect CSS flex wrap event


I have flex container with items inside. How to detect flex wrap event? I want to apply some new css to elements that have been wrapped. I suppose that it is impossible to detect wrap event by pure css. But it would be very powerful feature! I can try to "catch" this break point event by media query when element wraps into new line/row. But this is a terrible approach. I can try to detect it by script, but it's also not very good.

Look at the picture

I am very surprised, but simple $("#element").resize() doesn't work to detect height or width changes of flex container to apply appropriate css to child elements. LOL.

I have found that only this example of jquery code works jquery event listen on position changed

But still terribly.

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 be used with window.resize to detect for wrapping as the window is resized. 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'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 sort)

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.find('> *'), //select flex items
                        frst = chldrn.eq(0);

                    chldrn.each(function() {
                        var chld = $(this);
                        
                        chld.toggleClass('flex-wrapped', chld.offset().top != frst.offset().top); //element has wrapped
                    });
                    
                    prnt.toggleClass('flex-wrapping', !!prnt.find('.flex-wrapped').length); //wrapping has started
                    frst.toggleClass('flex-wrapped', prnt.find('> *:not(.flex-wrapped)').length == 1); //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-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>