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

Ana*_*Ana 9 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>
Run Code Online (Sandbox Code Playgroud)

样式非常基本,只需设置网格:

$w: Min(8em, 100%);
$s: .5rem;

.grid--masonry {
  display: grid;
  grid-template-columns: repeat(auto-fit, $w);
  grid-gap: $s;
  padding: $s;
    
  > * { width: $w }
}
Run Code Online (Sandbox Code Playgroud)

在 JS 中,第一步是获取网格、它的元素节点(过滤掉文本节点)、网格间隙和初始化列数:

然后,在页面加载后,我调用layout()函数:

addEventListener('load', e => {     
  layout(); /* initial load */
  addEventListener('resize', layout, false) /* on resize */
}, false)
Run Code Online (Sandbox Code Playgroud)

该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`
          }
        }
      })
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我本来希望这行得通,但事实并非如此。它似乎适用于两列,但不适用于更多列。我就是想不通为什么。

<section class='grid--masonry'>
    <img src='black_cat.jpg' alt='black cat'/>
    <!-- and so on, more images following the first -->
</section>
Run Code Online (Sandbox Code Playgroud)
$w: Min(8em, 100%);
$s: .5rem;

.grid--masonry {
  display: grid;
  grid-template-columns: repeat(auto-fit, $w);
  grid-gap: $s;
  padding: $s;
    
  > * { width: $w }
}
Run Code Online (Sandbox Code Playgroud)
addEventListener('load', e => {     
  layout(); /* initial load */
  addEventListener('resize', layout, false) /* on resize */
}, false)
Run Code Online (Sandbox Code Playgroud)

Ily*_*syn 5

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

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);
Run Code Online (Sandbox Code Playgroud)
.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%);
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)