瀑布流式布局(Masonry Layouts)

  • 瀑布流布局其核心是基于一个网格的布局,而且每行包含的项目列表高度是随机的(随着自己内容动态变化高度),同时每个项目列表呈堆栈形式排列,最为关键的是,堆栈之间彼此之间没有多余的间距差存大
  • 瀑布流布局的插件:Masonry、Isotope等

一. 纯CSS实现瀑布流布局

  • CSS的技术更新日新月异,在这几年当中出现了很多新的布局方法: 
    • 多列布局multi-columns、
    • Flexbox布局
    • 2017年浏览器支持有Grid布局。

1. CSS3 的 Multi-columns 方式

  • 通过Multi-columns相关的属性column-countcolumn-gap配合break-inside来实现瀑布流布局。
  • HTML结构:

    <div class="masonry">
      <div class="item">
         <div class="item__content"></div>
      </div>
      <div class="item">
         <div class="item__content"></div>
      </div>
      <div class="item">
         <div class="item__content"></div>
      </div>
      <!-- more items -->
    </div>
     
    • div.masonry是瀑布流的容器,其里面放置了n个列表div.item
  • CSS样式:

    • .masonry中设置column-countcolumn-gap。前者用来设置列数,后者设置列间距

      .masonry {
        column-count: 5; 列数
        column-gap: 0; 列间距
      }
    • 当初纯CSS实现瀑布流布局中最关键的是堆栈之间的间距,而并非列与列之间的控制(列与列之间的控制float之类的就能很好的实现)。

    • 在CSS中有一个break-inside属性,这个属性也是实现瀑布流布局最关键的属性。

      .item {
         break-inside: avoid; 用于控制文本块分解成单独的列,以免项目列表的内容跨列,破坏整体布局。
         box-sizing: border-box;
         padding: 10px;
      }
    • 当然为了布局具有响应式效果,可以借助媒体查询属性,在不同的条件下使用column-count设置不同的列,比如:

      .masonry {
         column-count: 1; // one column on mobile
      }
      @media (min-width: 400px) {
         .masonry {
             column-count: 2; // two columns on larger phones
         }
      }
      @media (min-width: 1200px) {
         .masonry {
             column-count: 3; // three columns on...you get it
         }
      }

2. CSS3 的 Flexbox 方式

  • 使用Flexbox实现瀑布流布局有2种方案。

2.1 一个主要的列容器

  • HTML结构不变, 和上面相同。
  • CSS样式改变:

    .masonry{
       display: flex;
       flex-flow: column wrap;
       width: 100%;
       height: 800px;
    }
    • flex-flow:控制列。并且允许它换行。
    • 注意: 必须显示的设置.masonry容器的高度,否则容器无法包裹住项目列表。
  • 使用Flexbox布局,对于.item可以不再使用break-inside: avoid,但其它属性可以是一样的。

  • 响应式设置,使用Flexbox多列布局更容易实现。

    • 注意: 响应式设计中,同样需要在不同的媒体查询条件下显示的给.masonry容器设置不同的高度
      .masonry { height: auto; }
      @media screen and (min-width: 400px) { .masonry { height: 1600px; } }
      @media screen and (min-width: 600px) { .masonry { height: 1300px; } }
      @media screen and (min-width: 800px) { .masonry { height: 1100px; } }
      @media screen and (min-width: 1100px) { .masonry { height: 800px; } }
  • 总结: 这种解决方案最致命的地方:需要显示的给.masonry容器设置height。 
    • 对于响应式设计来说,这个更为不友好。
    • 当项目列表是动态生成,而且内容不好控制式,这种方案就很头疼了。

2.2 单独的列容器

  • HTML结构:

    <div class="masonry">
       <div class="column"> `div.column`: 列表项目的单独容器。
         <div class="item">
             <div class="item__content"> </div>
         </div>
         <!-- more items -->
       </div>
       <div class="column">
          <div class="item">
             <div class="item__content"> </div>
          </div>
          <!-- more items -->
       </div>
    </div>
    • div.item外面包了一层div.column
  • CSS样式: 
    • 在这个解决方案中,.masonry.column都通过display:flex属性将其设置为Flex容器。
    • 不同的是.masonry设置为行(flex-direction:row),而.column设置为列(flex-direction):
      .masonry { display: flex; flex-direction: row; }
      .column { display: flex; flex-direction: column; width: calc(100%/3); }

      实际中根据自己的设计来设置width。

  • 这种方案对应的 响应式设计,需要在不同的媒体查询下修改.column 的 width值。
    .masonry { display: flex; flex-direction: column; }
    @media only screen and (min-width: 500px) {
    .masonry { flex-direction: row; } }
    .column { display: flex; flex-flow: column wrap; width: 100%; }
    @media only screen and (min-width: 500px) { .column { width: calc(100%/5); } }

3. Gird 方式

Grid制作瀑布流,对于结构而言和Multi-columns示例中的一样。 
只不过在.masonry使用display:grid来进行声明:

.masonry {
   display: grid;
   grid-gap: 40px;
   grid-template-columns: repeat(3, 1fr);
   grid-auto-rows: minmax(50px, auto);
}

对于.item较为头疼,需要分别通过grid-rowgrid-column来指定列表项目所在的区域:

.masonry > div:nth-child(1) {
  grid-row: 1 / 4;
  grid-column: 1;
}

.masonry > div:nth-child(2) {
  grid-row: 1 / 3;
  grid-column: 2;
}
...
  • 在Grid中有自动排列的算法的属性: 
    • 如果没有明确指定网格项目位置,网格会按自动排列算法,将它最大化利用可用空间。
    • 如果在当前行没有可用位置,网格会自动搜索下一行,这样会造成一定的差距,浪费可用空间。
    • 可以把grid-auto-flowrow值改变auto,可以切换搜索顺序。
    • grid-auto-flow还可以接受另一个关键词。默认情况下,其值是sparse,但我们可以将其显式的设置为dense,让网格项目试图自动填补所有可用的空白空间。
  • 言外之意,Grid中自动排列的算法对于实现瀑布流布局有很大的帮助。不过对于堆栈(列表项目)高度不能友好的控制。

总结 
主要介绍了如何使用纯CSS实现瀑布流的布局。 
文章简单介绍了三种实现方案:Multi-columns、Flexbox和Grid。 
从上面的示例或者实现手段而言,较我友好的是Flexbox的方案。 
当然,随着CSS Grid特性的完善,使用Grid实现瀑布流布局将会变得更为简单和友好。那让我们拭目以待。 
当然如果你觉得这些方案都不太好,你可以依旧可以考虑JavaScript的解决方案。

二、JS方式 和 JQuery方式

  • .html文件(三种方式都相同,就是内容,没啥说的):
    <head>
    <meta charset="UTF-8">
    <title>瀑布流首页</title>
    <link href="css/index.css" rel="stylesheet">
    </head>
    
    <body>
    <!--父盒子-->
    <div id="main">
       <!--单个盒子-->
       <div class="box">
           <!--图片盒子-->
          <div class="pic">
              <img src="images/1.jpg"/>
          </div>
       </div>
       <!--单个盒子标签复制20次...图片名字改改...-->
    </div>
    
    <!--jQuery方式需要引入jQuery库,JS方式与CSS方式都要注释掉-->
    <script src="js/jquery-3.1.1.min.js" type="text/javascript"></script>
    <!--引入JS,CSS方式注释掉-->
    <script src="js/index.js" type="text/javascript"></script>
    </body>
  • .css文件(JS方式与jQuery方式相同,CSS方式肯定要改):
    *{         /*去除边距*/
       margin:0;
       padding: 0;
          /*html性能优化:虚拟dom,如果一个html标签没有设置css样式,就是虚拟的,所以无论设置多少层div都对性能没有影响*/
    }
    #main{     /*定位*/
       position: relative;
    }
    .box{      /*内边距*/
       padding:15px 0 0 15px ;
       float: left;
    }
    .pic{       /*边框*/
       border:1px solid #dddddd;
    }
    .pic img{
       width: 165px;
    }

1. JS方式的.js文件:

function $(id) {
       //判断id的类型
    return typeof id === 'string' ? document.getElementById(id):id;
}

//当网页加载完毕
window.onload = function () {
    //瀑布流布局 保证传的参数能够找到父盒子
    waterFall('main','box');

    //滚动加载盒子
    window.onscroll = function (){
         if ( checkWillLoad() ){    //判断是否加载
         var data ={                 //创造假数据
             'dataImg':[
                 {'img':'23.jpg'},
                 {'img':'24.jpg'},
                 {'img':'25.jpg'},
                 {'img':'26.jpg'},
                 {'img':'27.jpg'},
                 {'img':'28.jpg'}
         ]};
        //加载数据
        for(var i=0; i<data.dataImg.length; i++){
              //创建最外面的盒子
           var newBox = document.createElement('div');
           newBox.className = 'box';
           $('main').appendChild(newBox);
                //创建单个盒子
           var newPic = document.createElement('div');
           newPic.className = 'pic';
           newBox.appendChild(newPic);
                //创建img
           var newImg = document.createElement('img');
           newImg.src = 'images/' + data.dataImg[i].img;
           newPic.appendChild(newImg);
        }
             //把刚创建的盒子瀑布流布局
        waterFall('main','box');
    }
  }
}

//实现瀑布流布局
//规则:从第二行开始的图片,总是拼接在上一行高度最矮的图片后面
function waterFall(parent,box) {
    //父盒子居中
          //通过父盒子拿到所有的子盒子
    var allBox = $(parent).getElementsByClassName(box);
          //求出盒子的宽度
    var boxWidth = allBox[0].offsetWidth;
        //求出浏览器的宽度(包括边框的宽高)
    var screenWidtn = document.body.offsetWidth;
        //求出列数 //取整函数取整
    var cols = Math.floor( screenWidtn/boxWidth);
    //父标签居中
        //先求出父标签宽度
    $(parent).style.width = boxWidth * cols + 'px';
        //居中
    $(parent).style.margin = '0 auto';

   //子盒子定位
       //创建一个高度数组,存所有的高度
   var heightArr = [];
       //遍历
   for(var i = 0; i < allBox.length ;i++){
          //求出每个盒子的高度
         var boxHeight = allBox[i].offsetHeight;
          //第一行的盒子不需要重新定位//每一行的盒子数与列数相同
         if(i<cols){
                 //添加第一行所有盒子高度
             heightArr.push(boxHeight);
          }else{           //剩下的盒子都需要瀑布流布局
                 //求出最矮的盒子高度
             var minBoxHeight = Math.min.apply(this,heightArr);
                 //求出最矮盒子对应的索引
             var minBoxIndex = getMinBoxIndex(minBoxHeight,heightArr);
                 //盒子瀑布流定位 顶部间距就是最矮盒子的高度
             allBox[i].style.position = 'absolute';
             allBox[i].style.top = minBoxHeight + 'px';
             allBox[i].style.left = minBoxIndex * boxWidth +'px';
                //关键:更新数组最矮高度,使下一个图片在高度数组中总是找最矮高度的图片下面拼接
             heightArr[minBoxIndex] += boxHeight;
          }
    }
}

//求出最矮盒子对应的索引函数
function getMinBoxIndex(val,arr) {
    for(var i in arr){
        if(val == arr[i]){
             return i;
        }
    }
}

//加载更多规则:当浏览器最下方到达图片的高度一半时让其刷新出来
        //判断是否符合加载条件
function checkWillLoad() {
       //取出所有盒子
    var allBox = $('main').getElementsByClassName('box');
       //取出最后一个盒子
    var lastBox = allBox[allBox.length - 1];
       //求出最后一个盒子高度的一半 + 内容与浏览器头部的偏离位置
    var lastBoxDis = lastBox.offsetHeight * 0.5 + lastBox.offsetTop;
       //求出浏览器的高度
      // 注意:JS代码存在浏览器兼容问题 一般分标准模式(按屏幕算document.body.offsetHeight)和混杂模式(按所有内容算)
    var screenHeight = document.documentElement.clientHeight;
       //页面偏离屏幕的高度
    var scrollTopHeight = document.body.scrollTop;
      //判断
    return lastBoxDis <= screenHeight + scrollTopHeight;
}

2:jQuery方式的.js文件:

简单介绍一下jQuery: 
JS存在的问题: 
1:浏览器兼容问题。 
2:JS存在复杂的DOM操作,实现特效动画复杂。 
3:请求网络数据存在跨域问题,跨域问题就是自动做代码保护,不允许跨域调用其他页面的对象, 
类似中国的墙'长城',即只能访问当前服务器的网址,无法访问外界的网址,解决方法百度上很多, 
但一般是后台处理。 
jQuery优势: 
1.几乎不存在浏览器兼容问题。 
2.轻松实现DOM操作,特效,动画。 
3.多种方式的网络请求方案

// document.write("<script src='jquery-3.1.1.min.js'></script>");
//当页面加载完毕
$(window).on('load',function () {
     //1.实现瀑布流布局
   waterFall();

     //2.滚动加载
$(window).on('scroll',function () {
    if (checkWillLoad()){       //判断是否加载
      var data ={ 'dataImg':[      //创造假数据
         {'img':'23.jpg'},
         {'img':'24.jpg'},
         {'img':'25.jpg'},
         {'img':'26.jpg'},
         {'img':'27.jpg'},
         {'img':'28.jpg'}
      ]};
        //遍历创建盒子
      $.each( data.dataImg, function (index,value){
              //创建一个div标签 设置它的类为'box' 添加到'main'里面去
          var newBox = $('<div>').addClass('box').appendTo($('#main'));
          var newPic = $('<div>').addClass('pic').appendTo($(newBox));
              //创建img 取出遍历的对象value的img属性对应的值
          $('<img>').attr('src','images/'+$(value).attr('img')).appendTo($(newPic));
      })

       //1.实现瀑布流布局
    waterFall();
     }
  });
});

//实现瀑布流布局
function waterFall () {
        //拿到所有的盒子
   var allBox = $('#main > .box');
      //取出其中一个盒子的宽度
   var boxWidth = $(allBox).eq(0).outerWidth();
     //取出屏幕的高度
   var screenWidth = $(window).width();
      //求出列数 //取整函数取整
   var cols = Math.floor( screenWidth/boxWidth);
     //父标签居中
   $('#main').css( {
      'width':cols * boxWidth + 'px',
      'margin':'0 auto'
   });
     //对子盒子定位
  var heightArr = [];
      //遍历
  $.each(allBox,function (index,value) {
     //取出单独盒子的高度
  var boxHeight = $(value).outerHeight();
     //判断是否第一行
  if(index < cols){
      heightArr[index] = boxHeight;
  }else {        //剩余的盒子要瀑布流布局
       //求出最矮的盒子高度
      var minBoxHeight = Math.min.apply(null,heightArr);
           //取出最矮高度对应的索引 封装了js的这个方法
      var minBoxIndex = $.inArray(minBoxHeight,heightArr);
        //定位
     $(value).css( {
        'position':'absolute',
        'top':minBoxHeight + 'px',
        'left':minBoxIndex * boxWidth + 'px'
     });
        //更新数组中最矮的高度
     heightArr[minBoxIndex] += boxHeight;
   }
 })
}

//判断是否符合加载条件
function checkWillLoad() {
          //直接取出最后一个盒子
   var lastBox = $('#main > div').last();
         //取出最后一个盒子高度的一半 + 头部偏离的位置
   var lastBoxDis = $(lastBox).outerHeight() + $(lastBox).offset().top;
         //求出浏览器的高度
   var clientHeight = $(window).height();
         //求出页面偏离浏览器高度
   var scrollTopHeight = $(window).scrollTop();
        //比较返回
    return lastBoxDis <= clientHeight + scrollTopHeight;
}

3:CSS方式修改一下.css中的内容即可:

主要是利用了css3中增加了一个新的属性:column 
具体看看这个很详细了:http://www.tuicool.com/articles/RvY3Yv

#main{
    /*定位*/
 /*position: relative;*/
   /*多栏布局 设置栏宽度*/
 -webkit-column-width:202px;
 -moz-column-width:202px;
 column-width:202px;
}

猜你喜欢

转载自blog.csdn.net/wei_jia007/article/details/82381025