简单介绍
瀑布流布局,在视觉上表现为由一系列等宽不等高的元素组成的多栏布局。
在实际案例中,瀑布流元素多为图片。
示意图如下:
三种实现方式
以瀑布流无限加载图片为例。说是三种实现方式,其实只有两种思路:
- 计算图片位置,利用绝对定位进行定位摆放
- 利用CSS3 的多栏布局
第一种方式可以由原生JS和jQuery实现,第二种方式利用CSS3 的column 属性实现。
HTML 布局
<body> <div class="box-group" id="waterfall"> <div class="box"> <div class="pic"><img alt="" src="image/1.jpg"></div> </div> <div class="box"> <div class="pic"><img alt="" src="image/2.jpg"></div> </div> <div class="box"> <div class="pic"><img alt="" src="image/3.jpg"></div> </div> <div class="box"> <div class="pic"><img alt="" src="image/4.jpg"></div> </div> <div class="box"> <div class="pic"><img alt="" src="image/5.jpg"></div> </div> ... </body>
|
方式一思路:
- 确定每个瀑布流元素的宽度w
- 列数c = Math.floor( 网页可见区的宽度 / 单个元素宽度w )
- 最外层包裹盒的宽度 = 列数c * 单个元素宽度w
- 建立临时高度数组,初始值为前c个元素的高度。
- 从第c+1个开始,每次计算当前高度数组中最小值,并将其插入到高度最小的位置,更新高度数组。
方式二思路:
- 确定每个瀑布流元素的宽度w
- 最外层容器设置
column-width
设置每列的宽度 或者 column-count
设置列数
原生JS
CSS代码
#waterfall{ position: relative; margin: 0 auto; } .box{ padding: 15px 0 0 15px; // display: inline-block; // vertical-align: top; float: left; } .pic{ padding: 10px; border: 1px solid #ddd; border-radius: 5px; box-shadow: 0px 0px 5px #ddd; } .pic img{ width: 300px; }
|
JS 代码
window.onload = function(){ var parentNode = document.getElementById('waterfall'); var boxNodes = document.getElementsByClassName('box'); var dataSet = {"images":[{"src":"image/1.jpg"},{"src":"image/2.jpg"},{"src":"image/3.jpg"},{"src":"image/4.jpg"},{"src":"image/5.jpg"}]}; waterfall(parentNode,boxNodes); window.onscroll = function(){ if(checkScrollSlide(parentNode,boxNodes)){ var images = dataSet.images; var imagesNum = images.length; var codeFrag = document.createDocumentFragment(); for (var i = 0; i < imagesNum; i++) { var boxNode = document.createElement('div'); boxNode.className = 'box'; codeFrag.appendChild(boxNode); var picNode = document.createElement('div'); picNode.className = 'pic'; boxNode.appendChild(picNode); var imgNode = document.createElement('img'); imgNode.src = images[i].src; picNode.appendChild(imgNode); } parentNode.appendChild(codeFrag); waterfall(parentNode,boxNodes); } } } function waterfall(parentNode,boxNodes){ var boxNum = boxNodes.length; if(boxNum){ var boxWidth = boxNodes[0].offsetWidth; var columnNum = Math.floor(document.body.clientWidth / boxWidth); parentNode.style.cssText = "width:"+boxWidth*columnNum+"px"; var heigthArr = []; for (var i = 0; i < boxNum; i++) { if(i<columnNum){ heigthArr.push(boxNodes[i].offsetHeight); }else{ var minHeight = Math.min.apply(null,heigthArr); var index = heigthArr.indexOf(minHeight); boxNodes[i].style.position = 'absolute'; boxNodes[i].style.top = minHeight+'px'; boxNodes[i].style.left = boxNodes[index].offsetLeft+'px'; heigthArr[index] += boxNodes[i].offsetHeight; } } } } function checkScrollSlide(parentNode,boxNodes){ var boxNum = boxNodes.length; if(boxNum){ var lastBoxTop = boxNodes[boxNum-1].offsetTop; var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; var clientHeight = document.body.clientHeight || document.documentElement.clientHeight; return lastBoxTop < (scrollTop + clientHeight) ? true: false; }else{ return false; } }
|
实现效果:
注意点:
1.计算元素绝对定位之前,要先使用float:left
使得元素有个初始定位。
(这里没有使用display:inline-block
的原因有两个:①对齐方式默认是vertical-align:baseline
,由于最开始的第一行需要顶部对齐,所以需要设置vertical-align:top
;②display:inline-block
设置后元素中间有不明原因的间隙)
float:left
的效果:
display:inline-block
的效果:
display:inline-block
+ vertical-align:top
的效果:
元素间间隙问题:
2.各种宽、高和距离问题
【offsetWidth】与【style.width】
同:都是当前对象的宽度(width
+ padding
+ border
)
异:
1) style.width
的返回值带有单位px
;
2) 对象宽度设置为百分比时,无论页面放大缩小,style.width
返回此百分比,offsetWidth
返回不同页面大小下的宽度具体值而非百分比;
3) offsetWidth
不能赋值,style.width
可赋值;
4) 如果没有给 HTML 元素指定过width
样式,则 style.width
返回的是空字符串。
【offsetHeight】与【style.height】同上
【offsetLeft】与【style.left】
同:都是当前对象距离父元素左边的距离
异:
1) style.left
的返回值带有单位px
;
2) 对象距离父元素左边的距离设置为百分比时,无论页面放大缩小,style.left
返回此百分比,offsetLeft
返回不同页面大小下的距离具体值而非百分比;
3) offsetLeft
不能赋值,style.left
可赋值;
4) 如果没有给 HTML 元素指定过left
样式,则 style.left
返回的是空字符串
【offsetTop】与【style.top】同上
【scrollWidth】获取对象的滚动宽度 。
【scrollHeight】 获取对象的滚动高度。
【scrollLeft】设置或获取位于对象左边界和对象中目前可见内容的最左端之间的距离(width+padding为一体)
【scrollTop】设置或获取位于对象最顶端和对象中可见内容的最顶端之间的距离;(height+padding为一体)
【clientWidth】 获取对象可见内容的宽度,不包括滚动条,不包括边框;
【clientHeight】获取对象可见内容的高度,不包括滚动条,不包括边框;
【clientLeft】 获取对象的border宽度
【clientTop】获取对象的border高度
【offsetParent】当前对象的上级层对象.
jQuery实现
$(window).on('load',function(){ var parentId = 'waterfall'; var boxClass = 'box'; waterfall(parentId,boxClass); var dataSet = {"images":[{"src":"image/1.jpg"},{"src":"image/2.jpg"},{"src":"image/3.jpg"},{"src":"image/4.jpg"},{"src":"image/5.jpg"}]}; $(window).on('scroll',function(){ if(checkScrollSlide(parentId,boxClass)){ var boxHtml = ''; $.each(dataSet.images,function(i,item){ boxHtml += '<div class="box"><div class="pic"><img src="'+item.src+'"></div></div>' }) $('#'+parentId).append(boxHtml); waterfall(parentId,boxClass); } }) }) function waterfall(parentId,boxClass){ var parentNode = $('#'+parentId); var boxNodes = $('.'+ boxClass); var boxNum = boxNodes.length; if(boxNum){ var boxWidth = boxNodes.eq(0).outerWidth(); var columnNum = Math.floor($(window).outerWidth() / boxWidth); parentNode.width(boxWidth*columnNum); var hArray = []; boxNodes.each(function(i,item){ if(i<columnNum){ hArray.push($(item).outerHeight()); }else{ var minHeight = Math.min.apply(null,hArray); var index = $.inArray(minHeight,hArray); $(item).css({ 'position':'absolute', 'top':minHeight+'px', 'left':index*boxWidth+'px' }) hArray[index] += $(item).outerHeight(); } }) } } function checkScrollSlide(parentId,boxClass){ var parentNode = $('#'+parentId); var boxNodes = $('.'+ boxClass); var boxNum = boxNodes.length; if(boxNum){ var lastBoxTop = boxNodes.last().offset().top; var scrollTop = $(window).scrollTop(); var clientHeight = $(window).height(); return lastBoxTop < (scrollTop + clientHeight) ? true : false; }else{ return false; } }
|
注意点:
1.jQuery 中 width()、innerwidth()、outerWidth()、outerWidth(true)
width()
:元素宽度
innerWidth()
:padding
+ 元素宽度
outerWidth()
:padding
+ border
+ 元素宽度
outerWidth(true)
:margin
+ padding
+ border
+ 元素宽度
2.window.onload 、$(window).on(‘load’,function(){})、$(document).ready(function(){})、document.addEventListener(‘DOMContentLoad’,function(){})
window.onload
、$(window).load
、$(window).on('load',function(){})
:
页面内包括图片、音视频加载完毕后才执行;window.onload
不能编写多个,如果有多个window.onload方
法,只会执行一个;$(window).load
新版本jQuery已经不支持,使用$(window).on('load',function(){})
$(document).ready(function(){})
、document.addEventListener('DOMContentLoad',function(){})
:
后者是前者的实质,DOM结构绘制完毕后就执行,不必等到加载完毕; $(document).ready()
可以同时编写多个,并且都可以得到执行 ;
$(document).ready(function(){})
可以简写成$(function(){})
。
3.减少DOM操作,提高性能
原生JS:利用createDocumentFragment()
实现。
jQuery节流:拼接HTML代码后一次性插入DOM
CSS3 多栏布局
CSS 代码
#waterfall{ position: relative; margin: 0 auto; column-width: 336px; } .box{ padding: 15px 0 0 15px; } .pic{ padding: 10px; border: 1px solid #ddd; border-radius: 5px; box-shadow: 0px 0px 5px #ddd; width: 300px; } .pic img{ width: 300px; }
|
实现效果:
实现方式比较
JavaScript 方式:
【优点】图片排序是按照图片计算的位置横向排列,比较规范。
【缺点】需要计算列数、图片的位置
CSS3 方式
【优点】不需要手动计算,浏览器会计算好。只需要设置列宽或者列数,性能高。
【缺点】
- 图片的无限加载还是要使用 JavaScript 来实现。
- 列宽会随浏览器窗口大小进行改变,用户体验不好。
- 图片是竖向一列一列排下来,图片顺序被打乱。而且有时候会出现元素内断行。
解决方式是在.box
上设置-webkit-column-break-inside: avoid;
(避免元素内断行)或者设置display:inblock
。
但是依然存在的问题是,由于box-shadow
设置的阴影部分不属于盒模型的大小,还是会有断行现象,这时需要再给.box
在设置一些padding
即可。
其他
git代码地址:三种方式实现瀑布流布局
主要学习视频:瀑布流布局
参考文章:JS中关于clientWidth offsetWidth scrollWidth 等的含义