重构项目之二:使用瀑布流效果加载图片
虽然这个功能最后使用了另外的插件,但是还是讲一下大概的原理吧,还是先上图:
功能描述:
1. 根据不同菜单的属性值分别加载不同的数据
2. 下拉滚动条到一定位置预加载图片,滚动条拉到最底下的时候渲染html;
3. 鼠标移到菜单,切换各个图片列表;
4. 鼠标移到图片列表上,显示详细信息;
技术实现方案:
先梳理一下从加载到显示的流程:
1. 加载数据
2. 拼接HTML写入到页面
3. 检查刚刚写入的HTML中的img是否全部加载完成,如果是,进入5、否则进入4
4. 等待图片加载完成
5. 计算每个元素的位置
一开始的时候最头疼的是如何定位的问题,后来经过朋友指导终于解决:
计算总共有多少列图片并且把每一列的高度都放到一个数组里面。每当一张图片加载完成的时候就查找这个数组里面最小的值,并且定位当前图片的top设置为这个值,完成后把这个图片的高度加上数组里面的最小值并且返回到数组里面,依次类推。
PS:因为这个功能代码太多,只能作基本的简单分解代码了:
1 // 创建用于记录每列高度的数组 2 _getLowestCol: function() { 3 t._cols = new Array(5),min = 0; 4 // 初始化为0 5 for (var i = 0; i < t._cols.length; i++) { 6 if (cols[i] < cols[min]) { 7 min = i; 8 } 9 return min; 10 } 11 }, 12 _reposition: function() { 13 t._grids.each(function(i, grid) { 14 //先显示出来 15 grid = $(grid).show(); 16 17 var height = grid.outerHeight(), min = t._getLowestCol(); 18 19 // 定位 20 grid.animate({ 21 left: (t._colWidth + t._colSpacing) * min, 22 top: t._cols[min], 23 opacity: 1 24 },1000); 25 // 记录高度 26 t._cols[min] += height; 27 }); 28 29 }
其次开发过程中遇到的难题是:
因为如上图所示,鼠标移动到菜单栏需要切换图片列表,并且分别需要用瀑布流加载不同类型的数据。所以要处理在切换页面的时候如何才能做到每个页面只执行一次代码请求接口,而不需要每一次切换都重新请求数据接口,仅仅执行切换显示图片列表的操作就可以了。
考虑到每一个菜单都有一个自定义属性,所以这个问题轻易地解决了:建立一个对象来记录当前菜单是否已经执行过代码,如果没有就执行请求数据 。
1 var isLoad = {};//是否载入过 2 labelType.mouseover(function() { 3 var i = $(this).index(); 4 var api = _this.attr('api');//接口标识 5 6 if(! isLoad[ api ]){ 7 isLoad[ api ] = i; 8 loadData(wrapper, api); 9 } 10 11 });
以下为全部代码:
html:
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title></title> 6 <style type="text/css"> 7 *{margin:0;padding:0;} 8 ul,li{ list-style-type:none;} 9 li img{width:100%;list-style:none;} 10 </style> 11 </head> 12 <body> 13 <div class="photo_box"> 14 <ul id="container" style="border:1px solid #000;width:80%;height:600px;overflow:hidden;margin:0 auto;position: relative;"> 15 </ul> 16 <div id="loading" class="loading" style="text-align: center;margin-top: 20px;font-size: 1.2em;">加载中...</div> 17 <div id="more" class="more"style="text-align: center;margin-top: 20px;font-size: 1.2em;"><input type="button" value="更 多" id="clear" /></div> 18 </div> 19 <script type="text/javascript" src="http://daisy.com/js-learning/resule/_example/waterFall_1.2/waterfall.js"></script> 20 <script type="text/javascript" src="http://x1.xiuimg.com/webjs/lib/jquery/jquery/1.7.2/jquery.js"></script> 21 <script type="text/javascript"> 22 23 waterFall.init({ 24 container: $('#container'), 25 dataURL: 'http://www.woxiu.com/index.php?action=Index/Main&do=ApiZhuboGrade', 26 dataType: 'jsonp', 27 colWidth: 200, 28 colSpacing: 10, 29 rowSpacing: 15, 30 page: 1, 31 pageEnd: 8, 32 }); 33 34 // 限制同时展示的页数 35 var loadCounter = 1; 36 function pageNum(){ 37 if (loadCounter >= 3) { 38 $('#more').show(); 39 $('#loading').hide(); 40 return true; 41 } else { 42 loadCounter++; 43 $('#more').hide(); 44 $('#loading').show(); 45 } 46 return false; 47 } 48 $('#clear').click(function() { 49 loadCounter = 1; 50 waterFall._loadNext(); 51 }); 52 53 54 </script> 55 </body>
js:
1 /** 2 * 瀑布流布局组件类 3 * @param {Object} options 组件设置 4 * @param {NodeList} options.container 瀑布流容器 5 * @param {String} options.dataURL 数据地址 6 * @param {String} [options.dataType='jsonp'] 数据类型,json或jsonp 7 * @param {String} options.template 模板编辑 8 * @param {Number} [options.colWidth] 图片大小。 9 * @param {Number} [options.colSpacing] 列间隔。 10 * @param {Number} [options.rowSpacing] 行间隔。 11 * @param {Number} [options.page=1] 数据开始页码 12 * @param {Number} [options.pageEnd] 数据末尾页码 13 14 * @pageNum() 函数,如果不需要现在加载也是,需要把函数里面的判断去掉。 15 16 17 从加载到显示的流程 18 19 1. 加载数据 20 2. 拼接HTML写入到页面 21 3. 检查刚刚写入的HTML中的img是否全部加载完成,如果是,进入5、否则进入4 22 4. 等待图片加载完成 23 5. 计算每个元素的位置 24 25 */ 26 27 28 var waterFall = { 29 init: function(options) { 30 var t = this; 31 t._container = options.container; 32 t._colWidth = options.colWidth; 33 t._colSpacing = options.colSpacing; 34 t._rowSpacing = options.rowSpacing; 35 t.dataURL = options.dataURL; 36 t.dataType = options.dataType; 37 t.page = options.page; 38 t.pageEnd = options.pageEnd; 39 t._switch = false; 40 41 //计算有几列 总宽度 / (列宽 + 列间隔) 42 t._totalCols = parseInt(t._container.width() / (t._colWidth + t._colSpacing)); 43 44 // 创建用于记录每列高度的数组 45 t._cols = new Array(t._totalCols); 46 // 初始化为0 47 for (var i = 0; i < t._cols.length; i++) { 48 t._cols[i] = 0; 49 } 50 51 t._loadingPage = options.page || 0; 52 t._loadNext(options); 53 54 //下拉滚动条加载 55 var lastTime = new Date().getTime(); 56 57 $(window).scroll(function() { 58 59 if ( !t._switch ) { 60 //判断是否滚动过快,在ie下 61 var thisTime = new Date().getTime(); 62 63 if (thisTime - lastTime < 50) { 64 console.log(thisTime - lastTime); 65 lastTime = thisTime; 66 return; 67 } 68 69 if ($(window).scrollTop() + $(window).height() >= document.documentElement.scrollHeight) { 70 lastTime = thisTime; 71 t._loadNext(); 72 } 73 } 74 }); 75 }, 76 //加载器 77 _loadNext: function(t) { 78 var t = this; 79 80 t._switch = true; 81 //请求数据 82 if (!t.trigger) { 83 $.ajax({ 84 url: t.dataURL, 85 data: { page: ++t._loadingPage }, 86 dataType:t.dataType, 87 success: function(response){ 88 89 t.trigger = t._completeLoading(response); 90 91 }, 92 error:function(){console.log('Error! 请求有误');} 93 }); 94 } 95 return false; 96 }, 97 //加载完数据调用此函数 98 _completeLoading: function(result) { 99 var t = this; 100 if (t._loadingPage >= t.pageEnd) { 101 $('#more').hide(); 102 $('#loading').html('<p>已是最后一页了喔 ^_^ ^_^</p>'); 103 return true; 104 } 105 else { 106 //if (!pageNum()) { 107 t._add(result); 108 //}; 109 } 110 111 return false; 112 }, 113 //添加格子 114 _add: function(result) { 115 var t = this, grids = ''; 116 var content = ''; 117 for(var i = 0; i < 10; i++) { 118 content += '<li style="display: none;">' + 119 '<img src = "http://tse1.mm.bing.net/th?id=OIP.Md8a5117c45764bb6064156cc2f08a5f4o0&w=135&h=272&c=7&rs=1&qlt=90&o=4&pid=1.9">' + 120 '</li>'; 121 } 122 123 console.log(content); 124 //原始定位 125 t._grids = $(content).css({ 126 position: 'absolute', 127 left: t._container.width(), 128 top: t._container.height(), 129 width: t._colWidth, 130 opacity: 0 131 }); 132 133 //把Html添加到容器 134 t._container.append(t._grids); 135 136 // 执行一次_reposition,如果所有图片都加载完成,该方法返回true,否则返回false 137 if ( !t._reposition() ) { 138 // 有图片未加载完,监听onload和onerror 139 t._grids.find('img').bind('load error', function() { 140 this.loaded = true; 141 // 有图片加载完成,再次执行_reposition 142 if (t._grids) { 143 t._reposition(); 144 } 145 }); 146 } 147 }, 148 // 此方法用于获取高度最低的列 149 _getLowestCol: function() { 150 var cols = this._cols, min = 0; 151 for (var i = 1; i < cols.length; i++) { 152 if (cols[i] < cols[min]) { 153 min = i; 154 } 155 } 156 return min; 157 }, 158 //定位 159 _reposition: function() { 160 161 var t = this, allImgsLoaded = true; 162 163 // 检测图片是否全部加载完成 164 t._grids.find('img').each(function(i, img) { 165 if (!img.loaded && !img.complete) { 166 167 allImgsLoaded = false; 168 } 169 return allImgsLoaded; 170 }); 171 172 if (allImgsLoaded) { 173 t._grids.each(function(i, grid) { 174 //先显示出来 175 grid = $(grid).show(); 176 177 var height = grid.outerHeight(), min = t._getLowestCol(); 178 179 // 非第一行的时候,要加上行间隔 180 if (t._cols[min]) { t._cols[min] += t._rowSpacing; } 181 // 定位 182 grid.animate({ 183 left: (t._colWidth + t._colSpacing) * min, 184 top: t._cols[min], 185 opacity: 1 186 },1000); 187 // 记录高度 188 t._cols[min] += height; 189 }); 190 // 重设外层容器高度为最高列高度 191 t._container.css( 'height', Math.max.apply(Math, t._cols) ); 192 t._switch = false; 193 delete t._grids; 194 } 195 196 return allImgsLoaded; 197 }, 198 }
PS:欢迎来我秀欣赏美女直播喔(*^__^*) :http://www.woxiu.com/
【全都是流水账,仅仅记录进步的点滴】