jquery实现简单的瀑布流布局


Posted in Javascript onDecember 11, 2016

是开头都会说的原理

瀑布流布局有两种,一种是固定列,一种是非固定列。在此主要记述第一种的实现。

固定列的特征是:无论页面如何缩放,每行的总列数都一致。

一行4列的瀑布流从布局的角度来说,就是4个li标签。通过一定的事件(比如滚动条滚动多少px),然后读取之,再把数据动态地添加到页面中。

添加数据原则,不是根据li索引值来加,而是根据各列中高度最短的的那列动态添加。否则可能导致页面很难看(左右高度不统一)。

实例涉及ajax方法。可在服务器环境下运行。

废话不多说了。直接上样式。

<ul id="ul1">
 <li>
  <div>
  <img src="images/1.jpg">
  <p>我是文字描述我是文字描述我是文字描述我是文字描述我是文字描述我是文字描述</p>
  </div>
 </li>
 <li>
  <div>
  <img src="images/2.jpg">
  <p>我是文字描述我是文字描述我是文字描述我是文字描述我是文字描述我是文字描述</p>
  </div>
 </li>
 <li>
  <div>
  <img src="images/3.jpg">
  <p>我是文字描述我是文字描述我是文字描述我是文字描述我是文字描述我是文字描述</p>
  </div>
 </li>
 <li>
  <div>
  <img src="images/4.jpg">
  <p>我是文字描述我是文字描述我是文字描述我是文字描述我是文字描述我是文字描述</p>
  </div>
 </li>
 </ul>

css

*{
 margin:0;
 padding: 0;
}
ul li{
 list-style: none;
}
#ul1{
 width: 1080px;
 margin: 100px auto 0;
}
li{
 width: 247px;
 float: left;
 margin-right: 10px;
}
li div{
 border:1px solid #000;padding:10px;
 margin-bottom:10px;
}
li div img{
 width: 225px;display: block;
}

基本效果如图:

jquery实现简单的瀑布流布局

样式显示没问题之后,就把li里面的代码删掉。

接下来通过ajax来动态添加。

数据哪里来?

这里用的是wookmark的数据接口。

http://www.wookmark.com/api/json/popular?page=1

点开url得到是一个json。

信息量很大。怎么分析?

一般可以看文档。但是手头没有文档的情况下,可以看看链接。返回是什么鬼。

function createUrl(num){
 return 'http://www.wookmark.com/api/json/popular?page='+num+'&callback=?';
}
$(function(){
 $.getJSON(createUrl(1),function(data){
 console.log(data);
 })
})

控制台打印结果为:

jquery实现简单的瀑布流布局

原来是一个50个图片信息组成的数组。每个数组元素都是一个json。在这个简单的demo里面,暂时只需要取preview属性和title属性就好了。

布局实现

关键之一在于,判断最短的li,事实上我们需要最短高度li的索引值。

//找出高度最小li的索引值
function getShortestLi(){
 var shortest=0;
 for(var i=1;i<4;i++){
 if($('li').eq(i).height()<$('li').eq(shortest).height()){
  shortest=i;
 }
 }
 return shortest;
}

然后就是getJSON方法

$(function(){
 $.getJSON(createUrl(1),function(data){
 //console.log(data);
 for(var i=0;i<dataArr.length;i++){
  var $html=$('<div><img src="'+data[i].preview+'"><p>'+data[i].title+'</p></div>');
  //console.log($('li').eq(getShortestLi()).height())
  $('li').eq(getShortestLi()).append($html);
 };
 console.log([$('li').eq(0).height(),$('li').eq(1).height(),$('li').eq(2).height(),$('li').eq(3).height()])
 })
})

再加载看看,布局就出来了。简单又漂亮。

jquery实现简单的瀑布流布局

做到这里,看起来一切都挺好。然而潜伏着一个致命的问题。

for循环惹的祸?

看看console.log的信息。为了分析,我把4个li的高度放进了一个数组:

jquery实现简单的瀑布流布局

50张图片分4列,少说平均高度也得有三四千像素。

而到循环结束,程序判断的终点竟然只有令人发指的1000多个px,因为图片加载过程慢于for循环执行速度。虽然demo里的显示还算正常,但这种代码在网速不好时,会造成工作事故。

思路一:可以判断图片是否加载完成。

可以用个定时器监听下,然后用递归实现,我的方案是这样

var index=0;
   function LoadPic(index){
   var $html=$('<div><img src="'+data[index].preview+'"><p>'+data[index].title+'</p></div>')
   $('li').eq(getShortestLi()).append($html);
   var oImg=$html.find('img');
   var t=setInterval(function(){
    if(oImg.height()!=0){//如果加载完了。
    clearInterval(t);
    //console.log([$('li').eq(0).height(),$('li').eq(1).height(),$('li').eq(2).height(),$('li').eq(3).height()])
    if(index<50){
     return LoadPic(index+1);
    }else{
     return false;
    } 
    }else{
    console.log('wait')
    }
   },50)//每隔50ms监听一次
   }
   LoadPic(0);

但是,从用户体验的角度来说,等一张图加载完成再进行下一张加载是不友好的。数据提供方都应该直接把图片的高度在服务器处理好,json数据里面返回过来。网速很慢的时候,要等好久,然后一下子图片都出来了,不觉得很诡异吗?尤其是第三方接口。一加载不出来就出大问题了。

所幸的是,第三方提供了图片的宽高信息。

因此for循环还是可以有的,在返回的数据里面,有宽度和高度值。利用它们就可以实现定宽(255px)和定高(原始高度乘上一个比例)。

$(function(){
 $.getJSON(createUrl(1),function(data){
 console.log(data);
 for(var i=0;i<data.length;i++){
  //console.log(data[i].preview);
  var $html=$('<div><img src="'+data[i].preview+'"><p>'+data[i].title+'</p></div>')
  $('li').eq(getShortestLi()).append($html);
  
  $html.find('img').css('height',(data[i].height*225/data[i].width)+'px');
  $html.find('img').css('width','225px'); 
  };
 //console.log([$('li').eq(0).height(),$('li').eq(1).height(),$('li').eq(2).height(),$('li').eq(3).height()])
 })
})

事实上个人认为这是最简单且用户体验最好的方案。

有了瀑布,还需要流

流的逻辑

往下拉(滚动),第一个底部进入可视区的li,优先加载。

jquery实现简单的瀑布流布局

换句话说,最短li的高度与该li到页面顶部之和小于滚动条高度和可视区高度之和时,触发li加载。

li的高度好求。但是最短li到页面顶部距离怎么求?

原生的方法可以这样实现:

function getTop(obj){
 var iTop=0;
 while(obj){
 iTop+=obj.offsetTop;
 obj=obj.offsetParent;
 }//累加元素本身和自身所有父级高度偏移值
 return iTop;
}

但是本案例既然是用jquery,自然有它的方法。

obj.offset().top

滚动事件

原生的实现方法是:window.onscroll=function(){...}

jquery的实现方法是:$(window).scroll(function(){...})

现在验证一下写出的代码代码有没问题

(window).scroll(function(){
 var $li=$('li').eq(getShortestLi());
 var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;
 //console.log([$li.offset().top+$li.height(),document.documentElement.clientHeight+scrollTop])
 //如果li高度与li到页面顶部的高度之和<可视区高度+滚动距离
 if($li.offset().top+$li.height()<document.documentElement.clientHeight+scrollTop){
  alert(1);
 }
 })

运行代码,发现第一个到底的li出现是可视区时,弹出1.证明可用。

因为涉及到滚动事件,所以getJSON相关函数应该封装为getList()方便调用。所以需要重新调整一下。

此时的代码是这样的:

//找出高度最小li的索引值
function getShortestLi(){
 var shortest=0;
 for(var i=1;i<4;i++){
 if($('li').eq(i).height()<$('li').eq(shortest).height()){
  shortest=i;
 }
 }
 return shortest;
}
function createUrl(num){
 return 'http://www.wookmark.com/api/json/popular?page='+num+'&callback=?';
}
function getList(n){
 $.getJSON(createUrl(n),function(data){
 //console.log(data);
 for(var i=0;i<data.length;i++){
  var $html=var $html=$('<div><img src="'+data[i].preview+'"><p>'+data[i].title+'</p></div>');
  //console.log(data[i].height);
  $('li').eq(getShortestLi()).append($html);
  dataArr[i].height*=225/dataArr[i].width;
  $html.find('img').css('height',dataArr[i].height+'px');
  $html.find('img').css('width','225px');
 }; 
}
$(function(){
 var pageNum=1;
 getList(pageNum);
 $(window).scroll(function(){
 var $li=$('li').eq(getShortestLi();
 var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;
 if($li.offset().top+$li.height()<document.documentElement.clientHeight+scrollTop){
  pageNum++;
  console.log(pageNum);
  getList(pageNum);
 }
 })
})

这样一来,好像可以实现了。但是一看控制台的console.log,又发现问题。

如厕的逻辑

在触发加载前提时,图片正在加载,期间动了滚动条,就又触发第二次加载,再动一下,就触发第三次,于是短短一瞬间,触发了n次加载。

那就做一个开关吧。

就跟公厕逻辑一样。n个人排队进一个坑位。外面的人想要进去首先得判断门是否锁上了。没锁才能进。进去之后第一件事把门锁上。等如厕完毕,门就打开。后面的人才能进

新设置一个开关bCheck,默认为true。

到触发加载条件时,还要判断bCheck是否为真(门开),为真时才能触发getList()(如厕)。否则return false(只能等)。

getList一开始就把bCheck设为false(如厕前先锁门)。等到getList回调函数执行到尾声。再把bCheck设为true(开门)。

这一段不贴代码了。

总有流完的一天。

当数据结束时(所有人上完厕所),就没有必要再进行加载了(自动把门锁上)。

所以在getJSON回调函数内锁门之后发现进来的是个空数组,那就进行判断,当获取到data的length为空时,直接returnfalse。那么bCheck就永远关上了。

全部代码如下:

//找出高度最小li的索引值
function getShortestLi(){
 var shortest=0;
 for(var i=1;i<4;i++){
 if($('li').eq(i).height()<$('li').eq(shortest).height()){
  shortest=i;
 }
 }
 return shortest;
}
function createUrl(num){
 return 'http://www.wookmark.com/api/json/popular?page='+num+'&callback=?';
}
var bCheck=false;
function getList(n){
 $.getJSON(createUrl(n),function(data){
 if(data.length==0){
  return false;
 }else{
  for(var i=0;i<data.length;i++){  
  //console.log(data[i].preview);
  var $html=$('<div><img src="'+data[i].preview+'"><p>'+data[i].title+'</p></div>');
  $('li').eq(getShortestLi()).append($html);
  
  $html.find('img').css('height',(data[i].height*225/data[i].width)+'px');
  $html.find('img').css('width','225px'); 
  };
 }
 bCheck=true; 
 });
}
$(function(){
 var pageNum=1;
 getList(pageNum);
 $(window).scroll(function(){
 var $li=$('li').eq(getShortestLi());
 var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;
 //console.log([$li.offset().top+$li.height(),document.documentElement.clientHeight+scrollTop])
 //如果li高度与li到页面顶部的高度之和<可视区高度+滚动距离
 if($li.offset().top+$li.height()<document.documentElement.clientHeight+scrollTop){
  if(bCheck){
  bCheck=false;
  pageNum++;
  //console.log(pageNum);
  getList(pageNum); 
  }else{
  return false;
  } 
 }
 })
})

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
基于jQuery的自动完成插件
Feb 03 Javascript
基于Jquery的仿照flash放大图片效果代码
Mar 16 Javascript
实例讲解JS中数组Array的操作方法
May 09 Javascript
纯jquery实现模仿淘宝购物车结算
Aug 20 Javascript
Javascript实现单例模式
Jan 24 Javascript
JSONP跨域请求
Mar 02 Javascript
es6的数字处理的方法(5个)
Mar 16 Javascript
vue+swiper实现侧滑菜单效果
Dec 28 Javascript
JS计算两个时间相差分钟数的方法示例
Jan 10 Javascript
解决layui的使用以及针对select、radio等表单组件不显示的问题
Sep 05 Javascript
原生JS实现弹幕效果的简单操作指南
Nov 10 Javascript
vue-cropper插件实现图片截取上传组件封装
May 27 Vue.js
js倒计时显示实例
Dec 11 #Javascript
jQuery实现模拟flash头像裁切上传功能示例
Dec 11 #Javascript
javascript实现将数字转成千分位的方法小结【5种方式】
Dec 11 #Javascript
JavaScript获取服务器时间的方法详解
Dec 11 #Javascript
基于jQuery实现的查看全文功能【实用】
Dec 11 #Javascript
AngularJS过滤器filter用法分析
Dec 11 #Javascript
jquery判断页面网址是否有效的两种方法
Dec 11 #Javascript
You might like
php编程实现获取excel文档内容的代码实例
2011/06/28 PHP
php中jpgraph类库的使用介绍
2013/08/08 PHP
PHP轻量级数据库操作类Medoo增加、删除、修改、查询例子
2014/07/04 PHP
php表单文件iframe异步上传实例讲解
2017/07/26 PHP
浅析PHP中的闭包和匿名函数
2017/12/25 PHP
PHP使用PDO、mysqli扩展实现与数据库交互操作详解
2019/07/20 PHP
javascript 基础篇4 window对象,DOM
2012/03/14 Javascript
Node.js生成HttpStatusCode辅助类发布到npm
2013/04/09 Javascript
js实现文本框宽度自适应文本宽度的方法
2015/08/13 Javascript
js实现仿网易点击弹出提示同时背景变暗效果
2015/08/13 Javascript
Javascript中的arguments对象
2016/06/20 Javascript
浅谈js的url解析函数封装
2016/06/28 Javascript
AngularJS教程之简单应用程序示例
2016/08/16 Javascript
javascript 分号总结及详细介绍
2016/09/24 Javascript
vue.js实现的绑定class操作示例
2018/07/06 Javascript
JavaScript实现多态和继承的封装操作示例
2018/08/20 Javascript
移动端吸顶fixbar的解决方案详解
2019/07/17 Javascript
vue 返回上一页,页面样式错乱的解决
2019/11/14 Javascript
小程序实现投票进度条
2019/11/20 Javascript
在Vue中创建可重用的 Transition的方法
2020/06/02 Javascript
Python数字图像处理之霍夫线变换实现详解
2018/01/12 Python
Python入门之后再看点什么好?
2018/03/05 Python
查看TensorFlow checkpoint文件中的变量名和对应值方法
2018/06/14 Python
Python使用Flask-SQLAlchemy连接数据库操作示例
2018/08/31 Python
python变量赋值方法(可变与不可变)
2019/01/12 Python
Python骚操作之动态定义函数
2019/03/26 Python
Python使用正则表达式分割字符串的实现方法
2019/07/16 Python
python 实现多维数组转向量
2019/11/30 Python
Pytorch 使用不同版本的cuda的方法步骤
2020/04/02 Python
python GUI模拟实现计算器
2020/06/22 Python
Pytorch mask-rcnn 实现细节分享
2020/06/24 Python
html5的websockets全双工通信详解学习示例
2014/02/26 HTML / CSS
女大学生自我鉴定
2013/12/09 职场文书
预备党员2014年第四季度思想汇报范文
2014/10/25 职场文书
思想政治表现评语
2015/01/04 职场文书
Python中使用subprocess库创建附加进程
2021/05/11 Python