手把手教你js原生瀑布流效果实现
什么是瀑布流效果
首先,让我们先看一段动画:
在动画中,我们不难发现,这个动画有以下特点:
- 1.所有的图片的宽度都是一样的
- 2.所有的图片的高度是不一样的
- 3.图片一张挨着一张竖直排列
- 4.鼠标向下滚动,一直不停的加载图片
- 5.浏览器的宽度改变,图片的列数会进行相应的更改
那么这种效果类似现实生活中的瀑布,所以我们叫它瀑布流的效果.
Js原生瀑布流效果的实现
从上述分析中,我们可以把整个效果分为以下四个部分:
- html+css界面搭建
- 瀑布流效果
- 浏览器向下滚动一直加载的效果
- 浏览器宽度改变,图片的列数改变的效果
那么,我们也从这四个分来实现
html+css界面搭建
从效果布局中我们发现,在整体的div标签里面,放置着无数div标签,用于固定每个图片的位置,在每个固定div位置的标签里面又有一个存放图片的div标签,用于图片的定位以及设置边框,在有存放图片的div标签里面放着一个img标签.如下图所示:
布局很简单,也不是这篇文章的重点,所以我就不多说,直接上代码.
html代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>瀑布流</title> <link rel="stylesheet" href="css/index.css"> </head> <body> <div id="outBox"> <div class="box"><div class="pic"><img src="data:images/1.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/2.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/3.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/4.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/5.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/6.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/7.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/8.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/9.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/10.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/11.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/12.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/13.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/1.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/2.jpg" alt=""></div></div> <div class="box"><div class="pic"><img src="data:images/3.jpg" alt=""></div></div> </div> <script src="js/Underscore-min.js"></script> <script src="js/MyFun.js"></script> <script src="js/index.js"></script> </body> </html>
css代码如下:
*{ margin:; padding:; border:; } img{ vertical-align: top; } #outBox{ position: relative; } .box{ padding: 15px 0 0 15px; float: left; } .pic{ padding: 10px; border: 1px solid #ccc; width: 240px; } .pic img { width: 100%; }
效果图如下:
实现瀑布流效果
实现最外层div居中显示
首先,我们可以想到给一个div设置居中显示需要两个步骤:1.给div设置宽度2.给div设置margin属性为auto即可实现div的水平居中显示.那么,我们应该给最外侧大的div设置多少的宽度呢?
我们再来回想一下上述动画效果,我们发现当浏览器宽度变大时,div宽度变宽,显示图片的列数增加,当浏览器宽度变小时,div宽度变窄,显示图片的列数减少.那么我们是不是可以理解为在div的宽度为:当前浏览器宽度下,显示最多的列数乘以div里面用于定位的div的宽度.所以接下来让我们来设置div的宽度及让其居中显示.我的代码每句话都有注释,所以我就不再单独讲解每段代码的含义.
js代码如下:
//首先将大盒子outBox居中排列 //要给大盒子设置宽度,大盒子的宽度为屏幕的宽度/小盒子box的宽度 取整 然后再乘以小盒子的宽度 //获取大盒子 var outBox=$(outBox); //获取大盒子中所有的小盒子 var allBoxs=outBox.children; //由于小盒子的宽度一样,所以随便找到一个小盒子,获取齐宽度 var boxWidth=allBoxs[0].offsetWidth; //获取浏览器的宽度 screenWidth=document.documentElement.clientWidth||document.body.clientWidth; //那么可以计算显示的列数就是浏览器的宽度除以每个小盒子的宽度取整 var cols=parseInt(screenWidth/boxWidth); //设置大盒子的宽度 outBox.style.width=cols*boxWidth +'px'; //设置大盒子的margin值 outBox.style.margin='0 auto';
效果图如下:
对除第一排之外的盒子做定位
首先,我们先了解,为什么是除第一排之外的盒子做定位,因为我们从上述图片中发现,第一排的位置摆放的非常整齐,我们并不需要对他们做定位.
那么,我们如何对第一排之外的盒子做定位呢?有人则认为从左至右逐一排列即可.那么由于所有的盒子高度不一样,我们假设一种情况所有的第一列的高度最大,第二列的高度最小,其它列依次增加,当我们排列的多了,会不会出现如下图所示非常丑陋的布局?
答案是肯定的,因为每张照片的长度都不一样,任何排列组合都有可能,所以我们不能从左到右依次排列,当然也不能从右至左依次排列.
接下来让我们想想,如果有一个容器,我们往里面放大小不等的物品,放进去的物品是不是会落到高度最小的那个位置?(如果理解不了的话,可以自己找个容器往里面丢东西试试.)所以我们排放图片也基于这个原理,把图片放高度最低的列,放置之后,更新列高为原列高+放置图片的高度.依次来放置图片即可.代码实现如下:
//遍历所有的box,然后将第一排(前cols个盒子的高度放入数组中) //创建一个空数组 var boxHeightArr=[]; //创建两个临时变量最小的高度以及盒子的高度 var minHeight=0,boxHeight=0; //遍历所有的盒子 for(var i=0;i<allBoxs.length;i++){ //获取遍历到每个盒子的高度 boxHeight=allBoxs[i].offsetHeight; if(i<cols){//讲第一排的高度放入数组中 boxHeightArr.push(boxHeight); }else { //然后将第一排后面的所有的元素放入之前数组中高度最小的那一列 //获取数组中最小的高度 minHeight=_.min(boxHeightArr); //计算最小高度的下标,通过遍历实现 var minIndex=getIndexByValue(boxHeightArr,minHeight); //设置当前盒子的样式,首先为固定定位 allBoxs[i].style.position='absolute'; //设置当前盒子的top,为最小高度 allBoxs[i].style.top= minHeight +'px'; //设置当前盒子的left为最小高度的下标乘以盒子的宽度,如果第三个为最小高度,那么当前盒子的left和第三个left一样,为3*盒子的宽度,因为盒子的宽度都一样 allBoxs[i].style.left= minIndex*boxWidth+'px'; //将数组中最小盒子的高度更新为当前高度+原来高度 boxHeightArr[minIndex]+=allBoxs[i].offsetHeight; } } } /* * 作用:通过数组中某一个元素,返回其在数组的索引 * 用法:getIndexByValue(数组,数组元素) * 返回值:返回数组元素在数组中的索引 * */ function getIndexByValue(arr,value) { //遍历数组 for(var i=0;i<arr.length;i++){ //输入数组中第i个元素的值和value一样,则返回i,i为最小宽度的下标 if(arr[i]==value){ return i; } } }
效果如下:
实现浏览器向下滚动一直加载的效果
首先,我们要先监听到浏览器向下滚动事件.其次我们需要计算出什么时候需要加载盒子.那么我们就定义一下,当最后一个盒子显示出来之后,我们就开始加载,那么最后一个盒子显示出来的判断条件是什么呢?请看下图.
从图中我们不难看出,当最后一个盒子的offsetTop值大于浏览器的高度+浏览器滚动的高度时,我们是在浏览器中看不到最后一个盒子的,所以最后一个盒子显示出来的条件为最后一个盒子的offsettop值大于浏览器的高度+浏览器滚动的高度.
接下来我们要实现自动创建盒子,这些dom的基本的知识,想必大家都很熟悉,那么问题来了,我们如何给盒子里面的img标签设置图片呢?当然,实际开发中我们是通过向服务器获取的方式获得图片的地址,但是现在这边文档的重点不是这个,所以我就随便创建了一个json,然后把数据事先写在json里面,需要用到的时候调用一下就行了.
判断是否需要加载的代码如下:
/* * 功能:判断是否需要加载图片 * 用法:willBeLoad() * 返回值:true 需要加载 false不需要加载 * */ function willBeLoad() { //1.先获取到最后一张照片,然后看它是否显示出来.如果显示则返回true,否则返回false //如果图片已经显示出来,那么这个图片到顶部的距离offsettop <屏幕的高度+滚动的高度 //获取所有的照片 var allBoxs=document.getElementsByClassName('box'); //获取最后一张图片,最后一张图片的下标为数组的长度减一 var lastBox=allBoxs[allBoxs.length-1]; //获取最后一张图片的offsetTop值 var lastBoxDis=lastBox.offsetTop; //获取屏幕的高度,这里需要适配不同的浏览器,所以使用||兼容一下 var screenHeight=document.documentElement.clientHeight||document.body.clientHeight; //获取滚动的高度 var scrollHeight=scroll().top; //判断,返回结果 if(lastBoxDis<=scrollHeight+screenHeight){ return true; }else { return false; } }
实现无限加载的js代码如下:
//实现向下滚动,无线加载图片 //监听滚动事件 window.onscroll=function () { if(willBeLoad()){//需要加载 //制造点假数据 var dataArr=[ {src:'1.jpg'}, {src:'5.jpg'}, {src:'3.jpg'}, {src:'4.jpg'}, {src:'2.jpg'}, {src:'9.jpg'}, {src:'8.jpg'}, {src:'6.jpg'}, {src:'7.jpg'}, {src:'10.jpg'}, {src:'12.jpg'}, {src:'13.jpg'}, ] //遍历数据 for(var i=0;i<dataArr.length;i++){ //创建一个新的盒子 var newBox=document.createElement('div'); //新盒子的className为box,和其它的样式一样 newBox.className='box'; //添加新盒子 $('outBox').appendChild(newBox); //创建一个新的pic盒子 var newPic=document.createElement('div'); //新盒子的className为pic,和其它的样式一样 newPic.className='pic'; //添加新盒子 newBox.appendChild(newPic); //创建一个img标签 var newImg=document.createElement('img'); //img标签的src是哪个从json数据中取出 newImg.src='images/'+dataArr[i].src; //添加新的img标签 newPic.appendChild(newImg); } //所有的盒子都创建完成之后,再执行一次瀑布流方法,否则新创建的标签不会实现瀑布流的效果 waterFall('outBox',screenWidth); } }
效果图如下:
浏览器宽度改变,图片的列数改变的效果
首先要监听浏览器宽度改变事件,然后当浏览器宽度改变之后,重新获取宽度,重新计算可以摆放的列数,然后加载瀑布流方法,这个比较简单,不多说,直接看代码即可
js代码如下:
window.onresize=function () {//监听窗口改变事件 //获取浏览器的宽度 screenWidth=document.documentElement.clientWidth||document.body.clientWidth; //重新执行瀑布流方法 waterFall('outBox',screenWidth); }
至此,所谓的js原生瀑布流效果已经实现,相信你看了这篇博客之后也发现js原生瀑布流并没有想象中的那么难,关键是思路要正确.