砖墙布局
具体原理,参考了腾讯砖墙布局的思路:http://isux.tencent.com/high-equal-response-layout-html.html
等高布局效果图:
如图,并不像等宽一样简单,要在不改变图片分辨率(宽高比)同时保持等高且占满行宽度,如何实现?不妨带着问题跟我走。
1 等高响应式布局是什么?
①行内高度相等;
②行间总宽度相等;
③自适应宽度布局;
④图片分辨率(宽高比)不变;
2 难在那里?
①行内高度一致,行间高度不一致,但是相差不能太多;
②并不知道一行需要多少个图片才能占满宽度,由于高度不确定,图片的宽度也不能等比变化;
③如何做到自适应?
④布局用于用户的个人相册,数据量是有限且未知的,如何保证图片数量满行显示?
由上可知,这种布局涉及太多变量,而且最难的是做到图片分辨率不改变,不影响图片质量效果。
该如下下手?我的思路是:确定一个变量,其他变量根据这个变量做适应性调整。
3 解决方法(具体下面会有图示)
①确定一个变量。由于当前的浏览器宽度是固定的,因此可以根据浏览器宽度范围制定一个标准高度,类似CSS的媒体查询(media query);
②初次变换。所有图片宽度根据这个标准高度作宽度的等比例缩放;
③创造容器。每行建立一个div,并装入尽可能多的图片,直到容器装不下;
④第一步调整。每行根据自己与目标宽度(当前浏览器宽度)的差值,再等比例变化宽、高。
公式如下:当前行总宽度/目标宽度=每个图片当前高度/变化后高度;
⑤第二步调整。根据变化后高度再等比变化各图片宽度;
4 操作图示
大工告成!然而深入考虑和分析,还总结出一些别的问题,我用了以下不同的处理方法把这些问题解决。
5 其他问题
①高度调整公式会产生百分比,浏览器是会直接取整,因此可能会产生-2到2px的误差;
解决方法:调整后记录每行误差值gap,然后循环把gap的值分给同行每一张图片,这样前2张图片可能会有±1px的图片宽度变化,但是用户基本觉察不了图片的轻微拉伸变化。
②用户图片数可能过少,会有图片只有1-3张占不满一行的情况,该怎样显示布局;
解决方法:判断只有1行图片的时候不作布局调整,少于1行则默认显示等高变化后的图片即可(即只调整一次,不需要为剩余值再自适应)。
③ 每行调整前的剩余宽度过大,导致调整后宽高很大;
解决方法:若调整后宽高是原始宽高的150%左右则该行舍弃,这里考虑到整体图片质量,确保不影响图片墙效果。
④ 用户上传的照片太小,例如16×16的小图标,如果一样的方式调整会与400×800这些图片并列放大,造成很大缩放比。
解决方法:考虑到是图片墙的效果,一般不会有用户传一些其他的图片,例如表情素材等等,同时在图片处理时可以加一个排序,获取了图片宽高后把小于一定值的图片排在最后再一起显示;
等高布局demo及实现代码
按照这个思路,我这里实现了一个demo,一般情况下我们去加载图片很多时候不知道每个图片的宽高值,这里采用onload加载完成获取每个图片宽高后再去处理的等高布局。
还有一点区别于以上思路,就是在上面第三步中“解决方法--③创造容器。每行建立一个div,并装入尽可能多的图片,直到容器装不下”,这句话是说放不下的时候排列进入下一行展示,我这里处理成的是进入本行展示。这样这个标准高度原本现在的是最小高度,而最大高度无法控制,我这里改动后这个标准高度就是最大高度,因为我这里的demo总宽度就这么大,控制下最大高度比较适合。而如果总宽度较大时用原思路的进入下一行展示的效果也是可以的。
简单的demo效果:
页面代码 BrickWall.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>布局展示</title> 6 <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script> 7 <style> 8 .grid { 9 width: 520px; 10 height: 1000px; 11 overflow: auto; 12 } 13 14 .grid li { 15 list-style-type: none; 16 margin: 0; 17 display: inline-block; 18 } 19 </style> 20 </head> 21 22 <body> 23 <div class="grid"> 24 </div> 25 <script type="text/javascript" src="BrickWall.js"></script> 26 <script> 27 var images = ["images/1.jpg", "images/2.jpg", "images/3.jpg", 28 "images/4.jpg", "images/5.jpg", "images/6.jpg", 29 "images/7.jpg", "images/8.jpg", "images/9.jpg", 30 "images/10.jpg", "images/11.jpg", "images/12.jpg", 31 "images/13.jpg", "images/14.jpg", "images/15.jpg", 32 "images/16.jpg", "images/17.jpg", "images/18.jpg", 33 "images/19.jpg", "images/20.jpg", "images/21.jpg", 34 "images/22.jpg", "images/23.jpg", "images/23.jpg", 35 "images/25.jpg", "images/26.jpg", "images/27.jpg", 36 "images/28.jpg", "images/29.jpg", "images/30.jpg", 37 ]; 38 new BrickWall(document.getElementsByClassName("grid")[0], images, 200, 500, null); 39 </script> 40 </body> 41 42 </html>
js代码
BrickWall.js文件
1 var BrickWall = (function () { 2 function BrickWall(container, imagesArr, baseHeight, totalWidth, callback) { 3 this.container = container; 4 this.imagesArr = imagesArr; 5 this.baseHeight = baseHeight; 6 this.totalWidth = totalWidth; 7 this.callback = callback; 8 this.imagesObj = []; 9 var imageCount = imagesArr.length; 10 var that = this; 11 imagesArr.forEach(function (src, i) { 12 that.imagesObj[i] = {}; 13 that.imagesObj[i].src = src; 14 var img = document.createElement("img"); 15 img.src = src; 16 that.imagesObj[i].imgEle = img; 17 img.onload = function (e) { 18 that.imagesObj[i].width = img.width * baseHeight / img.height; 19 ; 20 that.imagesObj[i].height = baseHeight; 21 imageCount--; 22 if (imageCount == 0) 23 that.onloadAll(); 24 }; 25 img.onerror = function (e) { 26 imageCount--; 27 if (imageCount == 0) 28 that.onloadAll(); 29 }; 30 }); 31 } 32 //所有图onload完成后,知道所有的宽高值后才能确定位置 33 BrickWall.prototype.onloadAll = function () { 34 var that = this; 35 var baseWidth = 0; 36 var divNum = 0; 37 var divTotalWidth = []; 38 that.imagesObj.forEach(function (imgObj, i) { 39 if (baseWidth < that.totalWidth) { 40 baseWidth += imgObj.width; 41 } 42 else { 43 divTotalWidth[divNum] = baseWidth; 44 divNum++; 45 baseWidth = imgObj.width; 46 } 47 imgObj.divNum = divNum; 48 imgObj.imgEle.height = that.baseHeight; 49 imgObj.imgEle.width = imgObj.width; 50 var li = document.createElement("li"); 51 li.appendChild(imgObj.imgEle); 52 that.container.appendChild(li); 53 // var first = that.container.firstChild;//得到页面的第一个元素 54 // that.container.insertBefore(li, first);//在得到的第一个元素之前插入 55 if (i == that.imagesObj.length - 1) 56 divTotalWidth[divNum] = baseWidth; 57 }); 58 that.imagesObj.forEach(function (imgObj, i) { 59 if (divTotalWidth[imgObj.divNum] > that.totalWidth) { 60 imgObj.imgEle.width = that.totalWidth / divTotalWidth[imgObj.divNum] * imgObj.width; 61 imgObj.imgEle.height = that.totalWidth / divTotalWidth[imgObj.divNum] * imgObj.height; 62 } 63 }); 64 }; 65 return BrickWall; 66 }());
倒序排列
另外项目加载图片需要砖墙布局,并且是倒序排列,提供以下几种方法。
1):扩展一个reverse反转某个元素下子元素的方法
1 $.extend({ 2 reverseChild: function (obj, child) { 3 var childObj = $(obj).find(child); 4 var total = childObj.length; 5 childObj.each(function (i) { 6 $(obj).append(childObj.eq((total - 1) - i)); 7 }); 8 } 9 });
2)使用css
.grid { overflow: auto; display: flex; flex-wrap: wrap-reverse; }
但此时滚动条就失效了,是个问题!
3)在插入列表的时候,每次都插入到列表的第一个位置
1 var li = document.createElement("li"); 2 var first = box.firstChild;//得到页面的第一个元素 3 box.insertBefore(li, first);//在得到的第一个元素之前插入