最近在探索实现真瀑布流功能时,发现大部分纯CSS
的方案都是假瀑布流,无法满足子元素不定高度的需求。然后我发现了一个使用Grid
布局来实现假瀑布流的方案,于是在此基础上进行了改造,并结合少量JavaScript
实现了真瀑布流。下面我将分享一下实现的基本原理以及一些关键技术细节。
实现效果
Demo 代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo</title>
<style>
body {
margin: 0;
}
.masonry {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 0 60px;
grid-auto-rows: 2px;
align-items: end;
}
.item {
background: #f8f8fa;
display: flex;
justify-content: center;
align-items: center;
}
@media (min-width: 1280px) and (max-width: 1920px) {
.masonry {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 768px) and (max-width: 1280px) {
.masonry {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.masonry {
grid-template-columns: repeat(1, 1fr);
}
}
</style>
</head>
<body>
<div class="masonry">
<div class="item">item1</div>
<div class="item">item2</div>
<div class="item">item3</div>
<div class="item">item4</div>
<div class="item">item5</div>
<div class="item">item6</div>
<div class="item">item7</div>
<div class="item">item8</div>
<div class="item">item9</div>
<div class="item">item10</div>
<div class="item">item11</div>
<div class="item">item12</div>
<div class="item">item13</div>
<div class="item">item14</div>
<div class="item">item15</div>
<div class="item">item16</div>
<div class="item">item17</div>
<div class="item">item18</div>
<div class="item">item19</div>
<div class="item">item20</div>
<div class="item">item21</div>
<div class="item">item22</div>
<div class="item">item23</div>
<div class="item">item24</div>
<div class="item">item25</div>
</div>
<script>
// 给每个元素模拟随机高度
window.addEventListener('load', () => {
document.querySelectorAll('.masonry > .item').forEach(item => {
item.style.height = `${Math.floor(Math.random() * 200) + 100}px`
})
})
const calcRows = () => {
const masonry = document.querySelector('.masonry')
const items = masonry.querySelectorAll('.item')
// 获取当前列数
const cols = getComputedStyle(masonry).gridTemplateColumns.split(" ").length;
items.forEach((item, index) => {
// 给需要上下间隔的元素增加上间隔(每列第一个元素无需上间隔)
const gapRows = index >= cols ? 8 : 0;
// 根据元素高度设置元素的需占行数
const rows = Math.ceil(item.clientHeight / 2) + gapRows;
item.style.gridRowEnd = `span ${rows}`;
})
}
window.addEventListener('resize', calcRows)
window.addEventListener('load', calcRows)
</script>
</body>
</html>
实现
基本原理
Grid
布局类似于Excel
表格,是一行一行排列的,无法让不定高度的项目依次堆叠在一起。因此,我们首先将Grid
网格设置得非常密集,通过grid-auto-rows: 2px
将每一行的网格设置为 2px 高度,然后通过JavaScript
获取元素高度并设置元素的需占行数,通过grid-row-end: span 行数
来实现堆叠。
如何保证排序是正确的
使用grid-row-end: span 行数
进行布局时,浏览器会自动排序,我们只需告诉浏览器每个卡片占据多少格,而不需要固定它们的位置。浏览器会自动堆叠,实现真正的瀑布流效果。由于JavaScript
的操作非常少,堆叠排布和元素宽度变化都由浏览器自动处理,因此性能损失很少。
元素间隔是如何实现的
由于我们的网格设置得非常小,一个瀑布流元素占据了一列的多个网格,所以无法直接使用gap
来实现行间隔(列间隔仍然可以使用gap-column-gap
实现)
我们首先尝试将行预期间隔加入到计算瀑布流元素高度的过程中,从而给瀑布流元素加上了一个下边距。然而,这种方法会导致最后一行的元素出现不应该有的下边距。由于瀑布流的特性,最后一行的元素并不确定,因此无法直接取消其下边距。
为了解决这一问题,我们转变了思路,尝试给瀑布流元素加上边距。通过使用align-items: end
属性让内容靠下,我们成功实现了上边距。随后,通过JavaScript
获取当前列数,我们成功地取消了每列第一个元素的上边距,从而实现了瀑布流元素间的间隔效果。通过这种方法,我们巧妙地解决了瀑布流元素间隔的问题,使得布局更加美观和整洁。
const masonry = document.querySelector('.masonry')
const items = masonry.querySelectorAll('.item')
// 获取当前列数
const cols = getComputedStyle(masonry).gridTemplateColumns.split(" ").length;
items.forEach((item, index) => {
// 给需要上下间隔的元素增加上间隔(每列第一个元素无需上间隔)
const gapRows = index >= cols ? 8 : 0;
// 根据元素高度设置元素的需占行数
const rows = Math.ceil(item.clientHeight / 2) + gapRows;
item.style.gridRowEnd = `span ${rows}`;
})