如何检测 CSS flex wrap 事件

Posted

技术标签:

【中文标题】如何检测 CSS flex wrap 事件【英文标题】:How to detect CSS flex wrap event 【发布时间】:2017-02-22 01:43:11 【问题描述】:

我有 flex 容器,里面有物品。如何检测 flex wrap 事件?我想将一些新的 css 应用于已包装的元素。我想不可能通过纯 css 检测到 wrap 事件。但这将是非常强大的功能!当元素换行到新行/行时,我可以尝试通过媒体查询“捕捉”这个断点事件。但这是一种可怕的方法。我可以尝试通过脚本来检测,但也不是很好。

我很惊讶,但是简单的 $("#element").resize() 无法检测 flex 容器的高度或宽度变化以将适当的 css 应用于子元素。哈哈。

我发现只有这个 jquery 代码示例有效 jquery event listen on position changed

但还是很糟糕。

【问题讨论】:

无法检测 CSS 中的换行 - 无论您使用的是 flexbox 还是 float,最好使用 media queries...跨度> 感谢您的回答。可能有一种简单的方法可以通过 JS 来检测它吗?我认为通过媒体查询“捕捉”弹性元素的浮动中断/包装点是没有意义的。 是什么导致项目换行 - 屏幕大小调整?添加更多元素? 是的。屏幕调整大小导致元素换行。我想适当地定位元素,为它们添加一些边距/填充,并在每个元素被包装后做一些其他的事情。总而言之,如果我能够检测到 flex wrapping 它会非常有用!很奇怪,flex wrapping 的断点是无法检测到的。 是什么导致元素首先换行?他们有min-width,还是内容阻止他们缩小所以他们换行了? 【参考方案1】:

这是一种可能的解决方案。您可能需要检查其他问题和边缘情况。

基本思想是循环遍历 flex items 并针对前一个兄弟测试它们的 top 位置。如果top 值更大(因此在页面下方),则该项目已包装。

detectWrap 函数返回一个已包装的 DOM 元素数组,可用于根据需要设置样式。

该函数可以理想地与ResizeObserver 一起使用(同时使用window's resize event 作为后备)作为触发器,以在调整窗口大小或由于脚本和其他用户更改页面中的元素时检查换行相互作用。因为 *** 代码窗口不会调整大小,所以在这里不起作用。

这是一个CodePen,可用于调整屏幕大小。

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>

【讨论】:

感谢您的帮助!我认为这个 sn-p 对每个人都会非常有用。总而言之,我认为 flexbox 需要新的 css 属性,例如“wrapped”或类似的东西来摆脱 js spaghetti。这个sn-p也可以改进。例如,在页面加载且尚未调整大小时立即检测所有包装元素。它现在重写了每个元素以前的 css 样式。 您将如何处理以下情况 - 假设您有 3 个项目。第一项全角。所以第 2 和第 3 项在第 2 行。第 3 件商品是否会被视为已包装? 好问题。 3-rd 元素不会被视为相对于 2-nd 项目的包装。这种情况下最好的办法是检查类的父元素是否具有“display:flex”css属性。然后您可以检查每个已包装的子项目。我需要改进 sn-p。【参考方案2】:

为此目的对 jQuery 的 sn-p 进行了少许改进。

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;
   );

【讨论】:

如果 flex-direction 是 column,则将 if 语句更改为:if (offset_top *') 来选择所有的弹性项目。【参考方案3】:

我正在使用类似的方法来确定 &lt;li&gt; 是否已包装在显示设置为 flex&lt;ul&gt; 中。

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';
            
        
    

【讨论】:

【参考方案4】:

我注意到元素通常会相对于第一个元素进行换行。将每个元素的偏移顶部与第一个元素进行比较是一种更简单的方法。这适用于换行和换行。 (如果元素使用弹性顺序,可能不起作用)

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>

【讨论】:

【参考方案5】:

如果有人想找到包裹元素开始的行的最后一个元素,可以使用以下逻辑。也适用于多行

       window.onresize = function (event) 
            const elements = document.querySelectorAll('.borrower-detail');
            let previousElement = ;
            let rowTop = elements[0].getBoundingClientRect().top;
            elements.forEach(el => el.classList.remove('last-el-of-row'))
            elements.forEach(el => 
                const elementTop = el.getBoundingClientRect().top;

                if (rowTop < elementTop) 
                    previousElement.classList.add('last-el-of-row');
                    rowTop = elementTop;
                

                previousElement = el;
            )
        ;

【讨论】:

【参考方案6】:

我已经修改了 sansSpoon 的代码,即使该元素不在页面的绝对顶部也可以正常工作。代码笔: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");
            
        
    

请注意,margin-top 不应应用于项目,因为它已被分解为 getBoundingClientRect,并将触发包装类应用于所有项目。

【讨论】:

我们应该使用for(of) 而不是Array.prototype.forEach btw。使用.forEach 会引入闭包并且性能更差。此外,在单个循环中读取和写入到 DOM 对 UX 来说确实很糟糕,因为浏览器需要在每次循环迭代之间执行布局操作。而是有两个单独的循环:第一个循环收集所有相关元素的大小信息,然后第二个循环修改 DOM(如切换 wrapped 类)而不读取任何会触发重新布局的属性。 是的,我不久前写了这篇文章,当时我还没有意识到 Array.prototype.forEach() 的缺点。可能应该重写我的解决方案。

以上是关于如何检测 CSS flex wrap 事件的主要内容,如果未能解决你的问题,请参考以下文章

检测浏览器中的 flex-wrap 支持

CSS 模拟点击事件——css 模拟聚焦事件——css 向下的角标显示——flex布局

CSS 模拟点击事件——css 模拟聚焦事件——css 向下的角标显示——flex布局

js 自定义事件 document.createEvent

css3 flex-wrap 当屏幕缩小后为啥多余的部分没有挤下去

如何使用flexbox css设置顶部和底部内容