等高响应式布局的原理和实现

砖墙布局

具体原理,参考了腾讯砖墙布局的思路:http://isux.tencent.com/high-equal-response-layout-html.html

等高布局效果图:

如图,并不像等宽一样简单,要在不改变图片分辨率(宽高比)同时保持等高且占满行宽度,如何实现?不妨带着问题跟我走。

1 等高响应式布局是什么?

①行内高度相等;
②行间总宽度相等;
③自适应宽度布局;
④图片分辨率(宽高比)不变;

2 难在那里?

①行内高度一致,行间高度不一致,但是相差不能太多;
②并不知道一行需要多少个图片才能占满宽度,由于高度不确定,图片的宽度也不能等比变化;
③如何做到自适应?
④布局用于用户的个人相册,数据量是有限且未知的,如何保证图片数量满行显示?

由上可知,这种布局涉及太多变量,而且最难的是做到图片分辨率不改变,不影响图片质量效果。

该如下下手?我的思路是:确定一个变量,其他变量根据这个变量做适应性调整。

3 解决方法(具体下面会有图示)

①确定一个变量。由于当前的浏览器宽度是固定的,因此可以根据浏览器宽度范围制定一个标准高度,类似CSS的媒体查询(media query);

②初次变换。所有图片宽度根据这个标准高度作宽度的等比例缩放;

③创造容器。每行建立一个div,并装入尽可能多的图片,直到容器装不下;

④第一步调整。每行根据自己与目标宽度(当前浏览器宽度)的差值,再等比例变化宽、高。

公式如下:当前行总宽度/目标宽度=每个图片当前高度/变化后高度;
⑤第二步调整。根据变化后高度再等比变化各图片宽度;

4 操作图示

 

step123

step45

大工告成!然而深入考虑和分析,还总结出一些别的问题,我用了以下不同的处理方法把这些问题解决。

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);//在得到的第一个元素之前插入 

 

posted @ 2017-07-18 16:42  方帅  阅读(957)  评论(0编辑  收藏  举报