Skip to content

瀑布流

瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。

image-20210729113939747

思路

首先第一步,我们仔细观察上面的瀑布流图片,你会发现他们都是定宽不定高的。

既然定宽,那么一共显示几列,我们也就能够计算出来。如下图所示:

image-20210729132058407

列数出来之后,我们拿一个数组来保存一行的高度。什么意思?看下图:

我们按照 4 列来算,一开始一张图片都没有放,每一列的高度都为 0,所以数组里面是 4 个 0

image-20210729132605594

接下来放入第一张图片,找数组里面最小的,目前都是 0,就放在第一列,放完之后需要更新数组里面的最小值

image-20210729132846593

然后依此类推,找数组最小的,会找到第二个 0,往第二列放入图片,更新数组,找到第三个 0,往第三列放入图片,更新数组...

image-20210729133210534

目前第一行满了,该放在第二行了,但是放在第二行的第几列呢?

实际上和上面的算法是一样的,找数组的最小值即可,哪个最小就放在哪一列,放完之后更新数组

image-20210729134002131

新的高度的计算公式:

js
这一列新的高度 = 这一列高度(数组里面存储的有) + 间隙 + 新的图片高度

然而这只是计算了 top 值,还有 left 值我们需要计算。每张图片的 left 值只和该图片所在的列有关。

image-20210729135724717

函数防抖

目前为止,图片已经按照瀑布流的形式布局出来了。但是当我们改变窗体大小的时候,图片是要重新进行布局的。

这就涉及到了要监听 widnow 的 resize 事件,每当此事件触发时,就需要重新排列。

重新排列倒是很简单,只需要把前面的制作思路封装成一个函数,重新调用这个函数即可。但是这里涉及到一个函数发抖的知识。

具体的代码片段如下:

js
/**
 * fn 回调函数
 * duration 延时时间
 */
function debounce(fn,duration){
    let timerId=null;
    return function(){
        clearTimeout(timerId);

        //返回函数-此处环境this指向是input元素
        const currThis=this;
        //arguments:伪数组-可以获取到函数的参数
        const args=Arrar.prototype.slice.call(arguments,0);
        
        setTimeout(()=>{
            fn.apply(currThis,args);//更改回调函数fn的this指向
        },duration)
    }
}

最佳实践

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片瀑布流2</title>
    <link rel="stylesheet" href="../css/index.css">
</head>
<body>
    <!-- 最外层容器 -->
    <div id="container" class="container">
        <!-- 里面的图片通过 js 来动态的生成 -->
    </div>
    <script src="../js/index.js"></script>
</body>
</html>
css
/* 外层容器样式 */
.container {
  width: 90%;
  margin: 0 auto;
  border: 1px solid #eee;
  position: relative;
}

/* 图片样式 */
.container img {
  position: absolute;
  transition: all 0.5s;
  width: 220px;
}
js
// 工具
// 防抖
function debounce(fn, duration) {
    let timerId = null;
    return function () {
        clearTimeout(timerId);

        //返回函数-此处环境this指向是input元素
        const currThis = this;
        //arguments:伪数组-可以获取到函数的参数
        const args = Array.prototype.slice.call(arguments, 0);

        setTimeout(() => {
            fn.apply(currThis, args);//更改回调函数fn的this指向
        }, duration)
    }
}


/* 初始化 */
var imgWidth = 220; // 每张图片的宽度
var divContainer = document.getElementById('container'); // 获取图片容器

// 该方法可以计算出几列
function cal() {
    var containerWidth = divContainer.clientWidth; // 容器的宽度
    // 列数 = 容器的宽度 / 图片的宽度
    var columns = Math.floor(containerWidth / imgWidth);
    // 还需要计算间隙
    // 总间隙 = 容器宽度 - (列数 * 图片宽度)
    var spaceNumber = columns + 1; // 间隙的数量
    var leftSpace = containerWidth - columns * imgWidth; // 计算剩余的空间
    var space = leftSpace / spaceNumber; // 每个间隙的空间
    return {
        space: space,
        columns: columns
    }
}

// 设置每一张图片的位置
function setPositions() {
    var info = cal(); //获取列数和间隙

    //创建数组,数组里面保存每一列的高度
    var arr = new Array(info.columns);
    arr.fill(0);

    // 计算每张图片 top 和 left
    for (var i = 0; i < divContainer.children.length; i++) {
        var img = divContainer.children[i];

        // 计算当前列top和left
        var minTop = Math.min(...arr);//寻找数组里面的最小值
        var index = arr.indexOf(minTop); //找到这个最小数所在列索引
        var left = (index + 1) * info.space + index * imgWidth;//计算该图left值

        // 更新该列高度  新的高度 = 图片的高度 + 间隙的高度
        arr[index] += img.height + info.space;

        // 设置图片dom的top和left
        img.style.top = minTop + "px";
        img.style.left = left + 'px';
    }

    // 更新容器高度(因为每张图都是绝对定位,脱离了常规流)
    var max = Math.max(...arr);
    divContainer.style.height = max + 'px';
}

// 创建图片,并且对图片的位置进行归位
function createImgs() {
    for (var i = 0; i <= 40; i++) {
        var src = `../img/${i}.jpg`;

        var img = document.createElement('img');
        img.src = src;
        img.style.width = imgWidth;
        divContainer.appendChild(img);

        // 当每张图片加载完毕后,都要进行重新排列
        img.onload = setPositions;
    }
}

//绑定事件 
function bindEvent() {
    window.onresize = debounce(setPositions, 500);
}

// 程序主函数
function main() {
    createImgs();//创建图片
    bindEvent();//绑定事件
}

main();

MIT License