CSS 瀑布流

    1.准备HTML与一些图片

    1. 准备CSS样式
    1. *{padding: 0;margin: 0;}
    2. .container{
    3. column-width: 350px;
    4. column-gap: 20px;
    5. padding: 0 20px;
    6. }
    7. .container div{
    8. width: 350px;
    9. overflow: hidden;
    10. margin-bottom: 10px;
    11. box-shadow: 0px 1px 1px 1px rgba(0,0,0,.21);
    12. }
    13. div img{
    14. width: 100%;
    15. }
    16. .container div{
    17. max-width: 100%;
    18. box-sizing: border-box;
    19. }

    代码量可以说非常的少,对于一些品质要求比较高的网站来说,这个方法并不是那么完善,它的排列顺序是上下左右的,可以看到阴影也有问题,不过未来的CSS可能更加强大。

    这里我先把实现瀑布流的流程给叙述一遍,增加大家的印象,这样我们就可以有迹可循的写出瀑布流。

    无论是什么瀑布流,它们的宽度都是相等,而高度是根据宽度不同,高度自动调整的。

    我们为了实现瀑布流居中,可以使用margin: 0 auto;,但是使用它的规制必须是宽度固定的,当然我们也可以使用其他布局方法比如flex,其实实现一些小功能有很多种方法,而多种方法组合起来,就是解决一个大功能的方法。

    那么我们如何知晓它的宽度呢?

    屏幕宽度我们是否可以知道呢? 是的,我们可以通过document.body.clientWidthdocument.documentElement.clientWidth获得它屏幕的宽度。

    现在我们已知总宽度,和一个盒子的宽度,问我们最多能放多少个盒子,那么这些盒子一起的宽度是多少呢?(没错,这就是一个小学数学应用题)。

    假设我们的屏幕宽度为let clientWith, 一个盒子的宽度为 let oneBoxWidth。 最多能放 let canLayBoxNum = Math.floor(clientWidth % oneBoxWidth), 此时我们可能还要往下取整,我们可以使用Math.floor,有的朋友可能不熟悉方法,我们可以把floor看成footer也就是脚,通常脚是在下面的,所以就是往下取整。那么一起的宽度为 let allBoxWidth = oneBoxWidth * canLayBoxNum

    现在我们解决了我们的第一个小问题,如何居中。

    其实这并不是教大家如何写代码,终有一天你写任何代码都没问题,但是解决问题的方法,与按部就班的思考才是精髓,这不仅体现在编程中,还体现在你所有的现实生活中。

    从第一个例子,我们现在已经非常清楚瀑布流的特效了,第一行是没有任何问题的,只要我们使用float:left; 就可以了,但是第二行开始,它会认为第一行的高度为,第一行里面高度最高的盒子。所以第二行与第一行之间将会有参差不齐的间隙,现在我们的问题就是解决这个现象?让盒子出现在我们想要的位置,我们通常用的方法就是postion:relative的父元素,postion:absolute的子元素,那么它的 lefttop又应该是多少呢?我们如何知晓呢?

    首先我们正常考虑,(此时我建议你在你的心里面构建一个矩阵,或者说棋盘。)把第二行的第一个盒子,放在第一行,第一个盒子的下面,拿到第一行第一个盒子的高度做为下面盒子的 top,left相同即可,但是这样有没有考虑这样一个问题,每一次第一个盒子的高度都是最短的呢? 要是来个20横行,那不就出现了第一竖列短的要命,其他的长的不像话,这样不好了。

    所以我们应该是在上一行高度最小的地方放置下一行的盒子,所以此时我们需要一个数组,来存储每一竖列当前的高度,而这个每竖列当前的高度,恰好可以作为我们当前添加盒子的top,而left,我们只需要知道它是在第几列就行了,第几列 * 盒子的宽度 = left。每次加上了一个新的盒子,是不是我们要更新当前的列高度啊,直接加上当前加上盒子的高度集合,然后接着找高度最小的列,继续添加盒子。

    接着按下Tab键就行了。

    1. #main{
    2. position: relative;
    3. }
    4. .box{
    5. float: left;
    6. padding: 15px 0 0 15px;
    7. }
    8. .pic{
    9. padding: 10px;
    10. border: 1px solid #ccc;
    11. box-shadow: 0 0 3px #ccc;
    12. border-radius: 5px;
    13. }

    首先我们解决居中问题

    紧接着我们完善我们的瀑布流逻辑, 修改一些变量和函数名,让其更容易被阅读

    1. function render(mainSelector, boxSelector){
    2. const main = document.querySelector(mainSelector);
    3. const boxArray = document.querySelectorAll(boxSelector);
    4. const oneBoxWidth = boxArray[0].offsetWidth;
    5. const boxCanLayNumber = Math.floor(document.documentElement.clientWidth / oneBoxWidth);
    6. const mainWidth = boxCanLayNumber * oneBoxWidth;
    7. main.style.cssText = `width:${mainWidth}px;margin:0 auto;`;
    8. let boxHeightArray = [];
    9. boxArray.forEach((box, index) => { // 遍历计算每一个盒子的left和top,并设置它们。
    10. if(index < boxCanLayNumber){ // 假如是第一行,那么直接就存储到数组里面
    11. boxHeightArray[index] = box.offsetHeight;
    12. }else{
    13. const minHeightValue = Math.min(...boxHeightArray); // 找到列里面最小的值。
    14. const minHeightIndex = boxHeightArray.findIndex((v) => v == minHeightValue); // 当前最小列的索引值
    15. box.style.position = 'absolute';
    16. box.style.left = minHeightIndex * oneBoxWidth + 'px'; // 当minHeightIndex为 0 刚好,left 就为 0;
    17. // box.style.left = boxArray[minHeightIndex].offsetLeft + 'px'; // 跟每一列第一个的左偏移量相同也可
    18. box.style.top = minHeightValue + 'px';
    19. boxHeightArray[minHeightIndex] += box.offsetHeight; // 增加当前列的高度
    20. }
    21. });
    22. }
    23. render('#main', '.box')

    此时,我们假如快速的刷新几次,我们会发现有一些问题,图片发生重叠,出现错误,接下来如何判断我完全是依照我自己的经验来的,我感觉boxHeightArray[minHeightIndex] += box.offsetHeight 这一行出现了问题,后来思考了一下,高度的问题,会不会是图片还没有加载完成,就直接计算了,导致高度不准确,所以我们把render放到window.onload里面去就可以了。

    我们把剩下的加载逻辑完成。逻辑也是非常的简单,当用户滚动的时候,滚到最后一个元素距离顶部的位置的时候,开始加载图片。

    以下是全部代码。

    因为有一张图比较大,所以我们添加一个css逻辑,固定以下宽度。

    1. .pic{
    2. padding: 10px;
    3. border: 1px solid #ccc;
    4. box-shadow: 0 0 3px #ccc;
    5. border-radius: 5px;
    6. max-width: 100%;
    7. }
    8. .pic img{
    9. width: 287px;

    所有代码例子都在我的 Github 可以找到。