阅读 455
详解瀑布流实现

详解瀑布流实现

瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式,视觉表现为参差不齐的多栏布局。最早采用此布局的是网站是 Pinterest,后逐渐在国内流行,比如花瓣,蘑菇街等。

瀑布流布局效果

1.png

css flex布局实现方式

最外面是一个容器,设置display为flex,使容器内部div横向排列,形成列,div内部通过flex-column,纵向排列。

<div class="tab-container">
    <div class="column">
        <div class="tab-item">1</div>
        <div class="tab-item height50">2</div>
    </div>
    <div class="column">
        <div class="tab-item">3</div>
    </div>
    <div class="column">
        <div class="tab-item height50">4</div>
        <div class="tab-item">5</div>
        <div class="tab-item height50">6</div>
    </div>
</div>
复制代码
* {
    margin: 0;
    padding: 0;
}
.tab-container {
    display: flex;
}
.column {
    flex-grow: 1;
    margin-right: 20px;
    display: flex;
    flex-direction: column;
}
.column:last-of-type {
    margin-right: 0;
}
.tab-item {
    height: 100px;
    background: white;
    margin-bottom: 10px;
}
.height50 {
    height: 50px;
}
复制代码

样式结果如下图

2021-08-14_190529.png 优点: 能轻松实现瀑布流静态效果,且性能不错。

缺点:

  1. 在填充元素前,需要对数据进行分列处理。
  2. 还需要手工对已经分完列的数据进行适当的调整,以防止相邻列内容高度差距过大。
  3. 若是动态添加的元素,则还是需要进行第一步的处理,同时对第二步的处理就无能为力了。

css column属性实现方式

多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次放入。

html部分

<div class="tab-container">
    <div class="tab-item">tab1</div>
    <div class="tab-item height50">tab2</div>
    <div class="tab-item height50">tab3</div>
    <div class="tab-item">tab4</div>
    <div class="tab-item height50">tab5</div>
    <div class="tab-item">tab6</div>
    <div class="tab-item">tab7</div>
    <div class="tab-item">tab8</div>
    <div class="tab-item height50">tab9</div>
    <div class="tab-item">tab10</div>
</div>
复制代码

css部分

* {
    margin: 0;
    padding: 0;
}
.tab-container {
  column-count: 3;
  column-gap: 30px;
}
.tab-container .tab-item{
  height: 150px;
  margin-bottom: 20px;
  break-inside: avoid;
  border: 1px solid #000;
  text-align: center;
  background: white;
}
.tab-container .height50 {
  height: 100px;
}
复制代码

样式结果如下图

7.png 优劣: 能轻松实现瀑布流静态效果,且性能不错。缺点是后续动态添加的内容,会在最右侧依次添加,整体交互体验不符合实际需求。故需要js处理动态添加的内容。

那么js处理的规则是什么呢?

下面通过图解来分析一下瀑布流的算法。

图解瀑布流算法

当第一排排满足够多的等宽图片时(如下图情况),自然而然的考虑到之后放置的图片会往下面排放。 2.png 那么第六张图片,放置在什么位置呢?是下图的位置么? 3.png 我们通过瀑布流算法实验得到,后面紧跟的第六张图片的位置应该是这个位置。 4.png 为什么呢? 因为放置它之前,这一列的高度为所有列中最小,所以会放置在这个地方。 所以我们知道了,如果再继续放置下去,第七张图片应该是这个位置,对吗? 5.png

通过瀑布流算法实验得出位置正确。看懂这个图示应该就能理解了瀑布流的原理算法。 6.png

JS方式实现瀑布流

思路分析:

  1. 构建成行元素 + 寻找新增元素追加位置

    瀑布流所有元素的宽度是固定的,我们用浏览器的宽度除以每个瀑布流块的宽度,就是每一行可容纳的瀑布流块的个数。因为每个瀑布流块的高度不一,我们姑且把组成一行的这组元素称为成行元素,在成行元素放置完毕后,我们如果要再增加一个元素,那么它的位置应该这样找?

    获取成行元素集合中高度最低的那个元素,待放置的元素的top值应该是这个最低元素的高,left值应该是这个最低元素的left值。

    这样,新增的这一个元素我们就找到了它存放的位置,这样成行元素中的最低高度值就变为了原来的高度+新增元素的高度。

  2. 重复1,依赖成行元素追加新元素。

    1中我们已经实现了一次成行元素追加一个新的元素,这样新元素增加之后我们就构建了新的成行元素,之后的操作就是在新的成行元素中追加新元素,原理同1。

html部分

<div class="tab-container" id="tabContainer">
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
    </div>
    <div class="tab-item">
	<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
    </div>
</div>
复制代码

css部分

* {
    margin: 0;
    padding: 0;
}
.tab-container {
    position: relative;
}
.tab-container .tab-item {
    position: absolute;
    height: auto;
    border: 1px solid #000;
    background: white;
    break-inside: avoid;
    text-align: center;
}
.tab-container .tab-item img {
    width: 100%;
    height: auto;
}
复制代码

js部分

window.onload = function () {
    waterFall('#tabContainer', '.tab-item'); //实现瀑布流
}

/**
 * @param { string } wrapIdName 容器id(或class)名称
 * @param { string } contentIdName 容器中内容项id(或class)名称
 * @param { number } column 容器中内容展示列数 默认为4
 * @param { number } columnGap 容器中列间隔距离 默认为20
 * @param { number } rowGap 容器中行间隔距离 默认为20
 */
function waterFall(wrapIdName, contentIdName, columns = 4, columnGap = 20, rowGap = 20) {
    // 获得内容可用宽度(去除滚动条宽度)
    const wrapContentWidth = document.querySelector(wrapIdName).offsetWidth - 8;

    // 间隔空白区域
    const whiteArea = (columns - 1) * columnGap;

    // 得到每列宽度(也即每项内容宽度)
    const contentWidth = parseInt((wrapContentWidth - whiteArea) / columns);

    // 得到内容项集合
    const contentList = document.querySelectorAll(contentIdName);

    // 成行内容项高度集合
    const lineConentHeightList = []

    for (let i = 0; i < contentList.length; i++) {
        // 动态设置内容项宽度
        contentList[i].style.width = contentWidth + 'px';

        // 获取内容项高度
        const height = contentList[i].clientHeight;

        if (i < columns) {
            // 第一行按序布局
            contentList[i].style.top = 0;
            contentList[i].style.left = contentWidth * i + columnGap * i + 'px';

            // 将行高push到数组
            lineConentHeightList.push(height)
        } else {
            // 其他行
            // 获取数组最小的高度 和 对应索引			
            let minHeight = Math.min(...lineConentHeightList)
            let index = lineConentHeightList.findIndex(listH => listH === minHeight)

            contentList[i].style.top = (minHeight + rowGap) + 'px';
            contentList[i].style.left = (contentWidth + columnGap) * index + 'px';

            // 修改最小列的高度 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 行间距
            lineConentHeightList[index] += height + rowGap
        }
    }
}
复制代码

代码处理结果

image.png

文章分类
前端
文章标签