javascript - 使用 getBoundingClientRect() 数据重新定位项目

标签 javascript

我正在尝试从 CSS 网格布局开始编写砌体布局(请不要建议使用库,是的,我知道新的 Firefox CSS 砌体功能,但我需要更好的支持)。
HTML 看起来像这样,一个包含一堆不同纵横比的图像的部分:

<section class='grid--masonry'>
    <img src='black_cat.jpg' alt='black cat'/>
    <!-- and so on, more images following the first -->
</section>
样式非常基本,只需设置网格:
$w: Min(8em, 100%);
$s: .5rem;

.grid--masonry {
  display: grid;
  grid-template-columns: repeat(auto-fit, $w);
  grid-gap: $s;
  padding: $s;
    
  > * { width: $w }
}
在 JS 中,第一步是获取网格、其元素节点(过滤掉文本节点)、网格间隙和 init 列数:
然后,在页面加载后,我调用 layout()功能:
addEventListener('load', e => {     
  layout(); /* initial load */
  addEventListener('resize', layout, false) /* on resize */
}, false)
layout()函数执行以下操作:
  • 获取 left每个网格项的偏移量
  • 由于这些偏移对于同一列上的项目是相同的,因此它会创建一组偏移,其大小是当前列数
  • 如果当前列数与 grid.ncol 相同我们已经有了,我们什么也不做,退出函数
  • 否则,我们更新 grid.ncol值然后...
  • 删除我们在网格项目上可能拥有的任何上边距,
  • 检查我们是否有超过一列
  • 如果我们只有一列,我们就退出 layout()函数,我们完成了!
  • 否则,对于每一列,我们得到它上面的项目(按偏移量过滤)
  • 如果我们只有一行,我们退出 layout()功能,仅此而已
  • 否则,我们遍历当前列上的项目,获取前一个项目的底部边缘和当前项目的顶部边缘,并计算我们需要将当前项目向上移动多少

  • 这是执行此操作的代码:
    function layout() {
      /* get left offset for every item of the grid */
      grid.items.forEach(c => { c.off = c._el.getBoundingClientRect().left });
    
      /* make a set out of the array of offsets we've just obtained */
      grid.off = new Set(grid.items.map(c => c.off));
                
      /* if the number of columns has changed */
      if(grid.ncol !== grid.off.size) {
        /* update number of columns */
        grid.ncol = grid.off.size;
                    
        /* revert to initial positioning, no margin */
        grid.items.forEach(c => c._el.style.removeProperty('margin-top'));
                    
        /* if we have more than one column */
        if(grid.ncol > 1) {         
          grid.off.forEach(o => { /* for each column */
            /* get items on that column */
            let col_items = grid.items.filter(c => c.off === o), 
                col_len = col_items.length;
                            
            /* if we have more than 1 item per column */
            if(col_len > 1) {
              for(let i = 1; i < col_len; i++) {
                let prev_fin = col_items[i - 1]._el.getBoundingClientRect().bottom /* bottom edge of item above */, 
                    curr_ini = col_items[i]._el.getBoundingClientRect().top /* top edge of current item */;
                                    
               col_items[i]._el.style.marginTop = `${prev_fin + grid.gap - curr_ini}px`
              }
            }
          })
        }
      }
    }
    
    我希望这行得通,但事实并非如此。它似乎适用于两列,但不适用于更多。我就是不知道为什么。

    let grid = { _el: document.querySelector('.grid--masonry'), ncol: 0 };
    
    grid.items = [...grid._el.childNodes].filter(c => c.nodeType === 1).map(c => ({ _el: c }));
    grid.gap = parseFloat(getComputedStyle(grid._el).gridRowGap);
    
    function layout() {
      /* get left offset for every item of the current grid */
      grid.items.forEach(c => { c.off = c._el.getBoundingClientRect().left });
      /* make a set out of the array of offsets we've just obtained */
      grid.off = new Set(grid.items.map(c => c.off));
    
      /* if the number of columns has changed */
      if(grid.ncol !== grid.off.size) {
        /* update number of columns */
        grid.ncol = grid.off.size;
    
        /* revert to initial positioning, no margin */
        grid.items.forEach(c => c._el.style.removeProperty('margin-top'));
    
        /* if we have more than one column */
        if(grid.ncol > 1) {         
          grid.off.forEach(o => { /* for each column */
            /* get items on that column */
            let col_items = grid.items.filter(c => c.off === o), 
                col_len = col_items.length;
    
            /* if we have more than 1 item per column */
            if(col_len > 1) {
              for(let i = 1; i < col_len; i++) {
                let prev_fin = col_items[i - 1]._el.getBoundingClientRect().bottom /* bottom edge of item above */, 
                    curr_ini = col_items[i]._el.getBoundingClientRect().top /* top edge of current item */;
    
                col_items[i]._el.style.marginTop = `${prev_fin + grid.gap - curr_ini}px`
              }
            }
          })
        }
      }
    }
    
    addEventListener('load', e => {     
      layout(); /* initial load */
      addEventListener('resize', layout, false) /* on resize */
    }, false);
    .grid--masonry {
      display: grid;
      grid-template-columns: repeat(auto-fit, Min(8em, 100%));
      justify-content: center;
      grid-gap: .5rem;
      padding: .5rem;
      background: violet;
    }
    .grid--masonry > * {
      width: Min(8em, 100%);
    }
    <section class='grid--masonry'>
        <img src='https://images.unsplash.com/photo-1510137600163-2729bc6959a6?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=900&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1587041403375-ddce288f4c49?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1580697895575-883f7c755346?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt=''/>
        <img src='https://images.unsplash.com/photo-1581200459935-685903de7d62?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1578264050450-ccc2f77796a1?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1557153921-10129d0f5b6c?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1584049086295-9f2af90efbb4?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1572196663741-b91b8f045330?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1558288215-664da65499af?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1592296109897-9c4d8e490e7a?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1525104885119-8806dd94ad58?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1576532116216-84f6a0aedaf6?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1533629947587-7b04aaa0e837?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1568386895623-74df8a7406f0?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1573777058681-73b866833d90?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1552566852-06d10a5050f4?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1576759470820-77a440a4d45b?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1586891622678-999a4419da34?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1584797318381-5958ca2e6b39?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1529093589387-b486dcc37c15?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1587421803669-b403d010dd80?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1570458436416-b8fcccfe883f?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1518206245806-5c1f4d0c5a2a?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
    </section>

    最佳答案

    看起来,由于 Grid 不同列中的项目不是独立的,因此更改较早行中单元格的高度会影响下一行中较早列中单元格的位置。将更新项目的顺序从“column-by-column”更改为“one-by-one”似乎可以解决它:

    let grid = { _el: document.querySelector('.grid--masonry'), ncol: 0 };
    
    grid.items = [...grid._el.childNodes].filter(c => c.nodeType === 1).map(c => ({ _el: c }));
    grid.gap = parseFloat(getComputedStyle(grid._el).gridRowGap);
    
    function layout() {
      /* get left offset for every item of the current grid */
      grid.items.forEach(c => { c.off = c._el.getBoundingClientRect().left });
      /* make a set out of the array of offsets we've just obtained */
      grid.off = new Set(grid.items.map(c => c.off));
    
      /* if the number of columns has changed */
      if(grid.ncol !== grid.off.size) {
        /* update number of columns */
        grid.ncol = grid.off.size;
    
        /* revert to initial positioning, no margin */
        grid.items.forEach(c => c._el.style.removeProperty('margin-top'));
    
        /* if we have more than one column */
        if(grid.ncol > 1) {         
          grid.items.forEach((item, n) => { /* for each item*/
            /* if we have more than 1 item per column */
            if(n >= grid.ncol) {
                let prev_fin = grid.items[n - grid.ncol]._el.getBoundingClientRect().bottom /* bottom edge of item above */, 
                    curr_ini = item._el.getBoundingClientRect().top /* top edge of current item */;
    
                item._el.style.marginTop = `${prev_fin + grid.gap - curr_ini}px`;
            }
          })
        }
      }
    }
    
    addEventListener('load', e => {     
      layout(); /* initial load */
      addEventListener('resize', layout, false); /* on resize */
    }, false);
    .grid--masonry {
      display: grid;
      grid-template-columns: repeat(auto-fit, Min(8em, 100%));
      justify-content: center;
      grid-gap: .5rem;
      padding: .5rem;
      background: violet;
    }
    .grid--masonry > * {
      width: Min(8em, 100%);
    }
    <section class='grid--masonry'>
        <img src='https://images.unsplash.com/photo-1510137600163-2729bc6959a6?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=900&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1587041403375-ddce288f4c49?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1580697895575-883f7c755346?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt=''/>
        <img src='https://images.unsplash.com/photo-1581200459935-685903de7d62?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1578264050450-ccc2f77796a1?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1557153921-10129d0f5b6c?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1584049086295-9f2af90efbb4?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1572196663741-b91b8f045330?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1558288215-664da65499af?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1592296109897-9c4d8e490e7a?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1525104885119-8806dd94ad58?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1576532116216-84f6a0aedaf6?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1533629947587-7b04aaa0e837?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1568386895623-74df8a7406f0?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1573777058681-73b866833d90?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1552566852-06d10a5050f4?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1576759470820-77a440a4d45b?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1586891622678-999a4419da34?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1584797318381-5958ca2e6b39?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1529093589387-b486dcc37c15?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1587421803669-b403d010dd80?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1570458436416-b8fcccfe883f?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
        <img src='https://images.unsplash.com/photo-1518206245806-5c1f4d0c5a2a?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='black cat'/>
    </section>

    关于javascript - 使用 getBoundingClientRect() 数据重新定位项目,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62951154/

    相关文章:

    javascript - 使用 onclick 函数动态添加表格行

    javascript - 驼峰字符串转普通字符串

    javascript - 事件精确路由器链接演示?

    javascript - 使用 "Possible strict violation."时 JSHint `bind`

    javascript - 使用 onMouseover 更改 <div> 的颜色

    javascript - 如何检测文件 ://URL fails in Firefox? 的脚本加载

    javascript - 单击不同行时如何提醒 id?

    javascript - jquery中如何设置弹出到右下角

    javascript - Meteor - 从 tr 数据属性获取 {{_id}},然后进行必要的 mongo 更改

    javascript - Node 类型错误: Promise resolver #<Promise> is not a function