我正在尝试编写一个瀑布流布局(请不要建议使用库,我知道新的Firefox CSS瀑布流功能,但我需要更好的支持),从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中,第一步是获取网格、它的元素节点(过滤出文本节点)、网格间距并初始化列数:
然后,在页面加载之后,我调用 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>