1. 什么是瀑布流?
瀑布流,又被称作为瀑布流式布局,是一种比较流行的网站页面布局方式,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。这种布局方式常见于一些图片为主的网站,就比如说一些常见的网站:
由上面的两张图我们可以很容易的明白什么是瀑布流式布局,可以看出,每个图片的高度是不一样的但是它们的宽度是统一的,每个图片之间的间隙也是均匀的。
总结瀑布流式布局的特征如下:
- 内容框宽度固定,高度不固定。
- 内容框从左到右排列,一行排满后,其余内容框就会按顺序排在短的一列后。
2. 为什么要用瀑布流布局
1、吸引用户
当用户在浏览瀑布流式布局的时候(这里抛开懒加载),用户会产生一种错觉,就是信息是不停的在更新的,这会激发用户的好奇心,使用户不停的往下滑动。
2.良好视觉体验
采用瀑布流布局的方式可以打破常规网站布局排版,给用户眼前一亮的新鲜感,用户在浏览内容时会感到很有新鲜感,带来良好的视觉体验。
3.更好的适应移动端
由于移动设备屏幕比电脑小,一个屏幕显示的内容不会非常多,因此可能要经常翻页。而在建网站时使用瀑布流布局,用户则只需要进行滚动就能够不断浏览内容。(这一点和懒加载有一点像)
3. 实现原理
我们已经总结出了瀑布流式布局的两大特征:
- 内容框宽度固定,高度不固定。
- 内容框从左到右排列,一行排满后,其余内容框就会按顺序排在短的一列后。
那么我们根据这两大特征就不难想出它的实现原理:
首先我们先通过计算一行能够容纳几列元素(因为我们需要在不同的设备上浏览),然后在通过计算比较找出这一列元素中高度之和最小一列,然后将下一行的第一个元素添加至高度之和最小的这一列的下面,然后继续计算所有列中高度之和最小的那一列,然后继续将新元素添加至高度之和最小的那一列后面,直至所有元素添加完毕。
可能饶了这么一大段话还是有小伙伴不明白,我们还是看图说话,这才是最容易理解的;
(1)首先我们放上一行
,它们高度不同,宽度相同:
(2)找出所有元素高度之和的最小的那列在那一列的下面添加新的元素
(3)然后继续计算,获取高度之和最小的那一列,在那一列继续添加新元素
看了这几张图是不是明确了很多,就是这样不停的计算,不停的添加新元素,直至所有元素添加完成。但是我们都知道一些元素是块级元素,一个元素占据一行,这里就会有小伙伴想到了,我们可以利用浮动啊,具体是不是,我们继续实验。
现在明白了瀑布流是布局是怎么实现的了,那么就来写具体的代码来实现一下吧!
4. 实现步骤及方法
1.通过浮动的方式进行布局
首先我们添加一些
元素,并设置为左浮动,分别给这些
元素给予不同的背景方便我们观察,注意,这些元素的宽度是一样的,只是高度不一样。
通过左浮动设置的布局
这就是通过左浮动设置的布局样式,我们可以看到在第二行的时候,第七个盒子浮动是根据第三个盒子来进行定位的。第三行的时候第七个盒子又挡住了第十个盒子的去路,所以这样我们是没法完成布局的
2.通过定位的方式进行布局
前面我们已经知道了浮动布局不能满足我们的需求,所以我们寻求另外一种布局方式,那就是通过定位,给每个盒子设置定位属性后,我们就可以通过动态的设置相应的top,left值来让盒子规规矩矩的为我们排列。
(一)给所有盒子的父元素加上相对定位属性,给所有盒子加上绝对定位属性:
position:relative; position:absolute;
(二)定位之后我们得到的界面是这样的:
(三)如何判断一行有多少列:
因为我们的界面宽度是变化的,所以列数也需要跟着变化,动态设置列数的原理大致如下:
1.获取到页面的宽度
2.获取到每个盒子的宽度
3.需要显示的列数 = 页面宽度/盒子宽度
**注意:**一般盒子之间都有一个间隙,所以我们的公式变成如下:
显示的列数(column) = 页面宽度(pageWidth)/(盒子宽度(itemWidth)+间隙(gap))
(四)排列第一行
- 我们有很多盒子,太多了我们就不好管理,所以我们把所有盒子装进一个数组arr里面,下标从0开始,用i来代表盒子。那么我们如何知道这个盒子是不是应该排列在第一行呢。其实很简单,想一下,我们这么多盒子,全部都要动态布局,动态的设置他的left、top值,那么for循环肯定是避免不了的,当**i(所有盒子的索引)<column(显示的列数)**的时候,盒子应该在第一行。比如说我们一行有5个盒子,那么第五个盒子的索引就是4,由于4<5,所以在第一行,如果索引为6,由于6>5,所以在第二行。代码基本就是这个样子
if (i < columns) { //确定第一行 items[i].style.top = 0; items[i].style.left = (itemWidth + gap) * i + 'px';
} - 知道了盒子在第1行之后,我们只需要动态的设置盒子的left值就能排列好了。
- 怎么动态计算每个盒子的left值呢:left=i*(itemWidth+gap),比如说我们排列第二个盒子,它的索引值i就是1,i*(itemWidth+gap)刚好就是第一个盒子和间隙的宽度和,所以距离left值也刚刚好。
说了这么多,画个图理解一下:
(五)获取所有列的高度和
- 在第一行排列好后,我们就需要排列第二行了,但是第二行我们需要用到top值了,前面原理的时候说了,我们就是根据每一列的最小高度和来进行排列,所以这里我们需要计算出第一行所有列的高度和并保存。
- 我们定义一个数组arr来保存高度。
我们需要在页面一进入的时候就获取高度,也就是写在onload里面,因为图片的加载特征是:等页面都加载完之后才去请求加载,所以不写在入口函数里可能会出现高度获取不到的情况。
(六)排列第二行
- 获取到刚刚数组中,高度最小的那一列,将第
2
行的第1
个盒子放置在它的下方; - 此时的
left
值就是高度最小列的offsetLeft
;top
值就是第1
行高度最小列的高度(为了布局美观可以加上上下间隙gap
)。 - 记录下高度最小列的索引
index
,后面计算会用到; - 设置完成之后,会发现后面所有的盒子都叠在这个高度最小列的下面,原因就是此时的最小列高度没有改变,应该加上下面盒子 的高度,得出一个新高度。我们需要在最小列后面加了一个盒子之后重新计算所有列的最小高度的列。
看图说话:我们看到第六个盒子下面下面全是其他盒子
(七)重新获取最小高度列的高度
- 排了第二行的第一个盒子之后,最小高度列已经发生了变化,我们需要重新计算一下
- 当前高度最小列的高度 = 当前高度最小列的高度 + 间隙 + 下面图片盒子的高度
看图说话:现在知道我们保留index做什么了吧。
(八)设置为响应式
- 将整个设置样式的部分封装成一个函数,在onload里面注册一个resize事件,只要页面一发生改变,就触发样式部分的代码。
实时改变pageWidth的宽度,这样瀑布流就会是一个响应式的效果了
(九)添加懒加载效果
懒加载的实现原理可以参考我的上一篇文章,说得很清楚详细。大致原理用一张图来描述
(十)所有代码(没有包含懒加载代码)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>瀑布流</title> <style> * { margin: 0; padding: 0; } .item { float: left; display: flex; justify-content: center; align-items: center; font-size: 30px; font-weight: 700; color: aliceblue; margin-right: 15px; margin-bottom: 15px; width: 205px; position: absolute; } .item-1 { background-color: rgb(206, 169, 169); height: 300px; }.item-2 { background-color: rgb(131, 226, 174); height: 150px; }.item-3 { background-color: rgb(77, 30, 30); height: 350px; }.item-4 { background-color: rgb(49, 62, 134); height: 300px; }.item-5 { background-color: rgb(230, 99, 99); height: 200px; }.item-6 { background-color: rgb(206, 169, 169); height: 300px; }.item-7 { background-color: rgb(124, 126, 145); height: 400px; }.item-8 { background-color: rgb(169, 199, 38); height: 230px; }.item-9 { background-color: rgb(114, 128, 53); height: 300px; }.item-10 { background-color: rgb(48, 54, 18); height: 260px; }.item-11 { background-color: rgb(118, 122, 96); height: 230px; }.item-12 { background-color: rgb(118, 122, 96); height: 240px; }.item-13 { background-color: rgb(118, 122, 96); height: 250px; }.item-14 { background-color: rgb(118, 122, 96); height: 270px; }.item-15 { background-color: rgb(118, 122, 96); height: 330px; }.item-16 { background-color: rgb(118, 122, 96); height: 200px; }.item-17 { background-color: rgb(118, 122, 96); height: 100px; }.item-18 { background-color: rgb(118, 122, 96); height: 400px; }.item-19 { background-color: rgb(118, 122, 96); height: 340px; }.item-20 { background-color: rgb(118, 122, 96); height: 350px; }.item-21 { background-color: rgb(118, 122, 96); height: 360px; }.item-22 { background-color: rgb(118, 122, 96); height: 370px; } </style> </head> <body> <div id="box"> <div class="item item-1">1</div> <div class="item item-2">2</div> <div class="item item-3">3</div> <div class="item item-4">4</div> <div class="item item-5">5</div> <div class="item item-6">6</div> <div class="item item-7">7</div> <div class="item item-8">8</div> <div class="item item-9">9</div> <div class="item item-10">10</div> <div class="item item-11">11</div> <div class="item item-12">12</div> <div class="item item-13">13</div> <div class="item item-14">14</div> <div class="item item-15">15</div> <div class="item item-16">16</div> <div class="item item-17">17</div> <div class="item item-18">18</div> <div class="item item-19">19</div> <div class="item item-20">20</div> <div class="item item-21">21</div> <div class="item item-22">22</div> </div> </body> <script> var items = document.getElementsByClassName('item'); //定义间隙10像素 var gap = 10; //进页面执行函数 window.onload = function () { waterFall(); } function waterFall() { //首先确定列数 = 页面的宽度 / 图片的宽度 var pageWidth = getClient().width; var itemWidth = items[0].offsetWidth; var columns = parseInt(pageWidth / (itemWidth + gap)); var arr = [];//定义一个数组,用来存储元素的高度 for(var i = 0;i < items.length; i++){ if(i < columns) { //满足这个条件则说明在第一行,文章里面有提到 items[i].style.top = 0; items[i].style.left = (itemWidth + gap) * i + 'px'; arr.push(items[i].offsetHeight); }else { //其他行,先找出最小高度列,和索引 //假设最小高度是第一个元素 var minHeight = arr[0]; var index = 0; for(var j = 0; j < arr.length; j++){//找出最小高度 if(minHeight > arr[j]){ minHeight = arr[j]; index = j; } } //设置下一行的第一个盒子的位置 //top值就是最小列的高度+gap items[i].style.top = arr[index] + gap + 'px'; items[i].style.left = items[index].offsetLeft + 'px'; //修改最小列的高度 //最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 间隙的高度 arr[index] = arr[index] + items[i].offsetHeight + gap; } } } //当页面尺寸发生变化时,触发函数,实现响应式 window.onresize = function () { waterFall(); } // clientWidth 处理兼容性 function getClient() { return { width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight } } // scrollTop兼容性处理 function getScrollTop() { return window.pageYOffset || document.documentElement.scrollTop; } </script> </html>
**注意:**如果要加入懒加载效果的话,可以参考我的之前一篇文章,讲的较为详细,由于这里没有在
里面添加图片,所以,不能实现懒加载效果。这里只设置了div,并且分别给div设置的高度,稍加繁琐。如果有合适的图片资源,可以不用设置。
5. 总结
到这里,我们的瀑布流式布局布局也算讲完了,讲得过程比较啰嗦,也是为了让更多的人能够轻松理解。从整个实现过程来看,我们主要有两个重点:
- 瀑布流式布局是利用定位来实现的,动态的改变元素的top和left值。
- 获取最小高度和的列并保存它的索引,以便让下一行的元素知道该放在哪里。
6. 源码
更多源码请移步GitHub:Javascript