图片来自 百度图片
像这样高度一样,而宽度不同的布局方式称之为木桶布局。它有几个鲜明的特点: 每行的图片高度一致;每行的图片都是占满的。
如何实现木桶布局 之 整体思路
我们需要先拥有一些素材(图片), 并且将这些图片横向摆放到页面上。假如我现在有100张图片,并且编号1~100, 它们的宽高都不是固定的。现在我们需要将图片都摆放到目标页面上。
首先,我们每行的高度是事先设定的,所以我们在把图片放入每一行(row)的时候,需要先调整图片的大小(等比例放大或缩小)使得图片的高度与行容器的高度一致 —— 即下图中的蓝色方框。
其中: 橙色方框代表图片(假设已和蓝色方框等高), 黑色数字代表图片编号。
依次往行容器中放置等比例大小调整过的图片,①号图片,②号图片,...,一直到⑤号图片,发现⑤号图片它放不下了。这时候我们就需要把这个⑤号图片放到下一行的开头,并且再次调整图片大小,使得第一行中的①号-④号图片能够撑满第一行。该次调整主要是调整图片的高度,使得其宽度能够自适应撑满该行。
如果测量一下百度图片每行的高度就会发现: 每行高度基本相等,但有几像素的差距。
然后重复上述步骤,直至图片摆放完毕。
木桶布局代码实现 之 具体步骤
从HTML、CSS、JS部分依次实现。
大概有个结构,就是先有一个固定宽度的父容器,与若干个(数量未定的)行容器来盛放每行的图片数量,然后每行有若干个图片容器来盛放图片。
父子关系(父 > 子): 固定宽度的父容器 (" .ct ") > 行容器(" .img-row ") > 图片容器(" .img-box ")
对应上图(木桶布局原理)为: 红色方框 > 蓝色方框 > 橙色方框
<!-- HTML -->
<div class="ct"></div>
<style type="text/css">
/* CSS */
/* 页面布局容器*/
.ct {
width: 1000px;
margin: 0 auto;
}
/* 图片容器 */
.img-box {
float: left;
}
/* 行容器 清楚子元素(图片容器)的浮动*/
.img-row::after {
content: "";
display: block;
clear: both;
}
</style>
JS代码
这里采用构造函数创建对象的方式来写这段代码,注意按照约定构造函数的首字母要大写。创建一个新对象,然后将构造函数的作用域赋给新对象,调用构造函数中的方法。
函数名声明为 Barrel
,意为木桶。然后就要确定有哪些属性和方法。在理解了思路步骤的前提下,可以构思需要哪些属性、方法以及它们的作用。
属性:
- 每行图片的高度固定: rowHeight, 行高
- 拥有一个固定的容器: DOM对象,一个容器 命名为
.ct
。 还应该有行容器和图片容器,但是由于这两个容器内容数量不固定,所以在布局的时候再创建 - 行容器的宽度: width, 获取ct的宽度
- 存放每行图片的数组: imgArr[]。每次把加载的图片压入该数组,判断该行是否超出宽度。
方法:
- 拥有素材图片 : 通过
getImgUrls()
方法来获取图片链接,(或从数据库中获取图片)。这里是通过访问https://placeholder.com/ 网站来获取代码,具体后述 - 加载图片信息:
loadImg()
方法来加载图片,以便获取图片信息, - 渲染图片队列:
render()
改变图片的比例大小,计算一行可以放置多少个图片 - 放置图片位置:
layout()
将改变完大小的图片放置到页面上,append到对应的DOM元素节点上。具体关系对应前面的父子关系即可
初步的代码结构就如下所示:
function Barrel(ct, imgNum, height) {
this.ct = ct; // 木桶布局容器的DOM节点
this.width = parseInt(window.getComputedStyle(ct, null).getPropertyValue("width")); // 行宽,由于获取到的值是string: 1000px 所以转化为数值 1000
this.rowHeight = height; // 行高
this.imgArr = []; // 存放每行图片的数组
this.loadImg(imgNum);
}
Barrel.prototype = {
getImgUrls: function(){},
loadImg: function(){},
render: function(){},
layout: function(){}
}
方法实现:
getImgUrls()方法:首先可以访问该网站 ,可以获取到占位图片,可以看到获取占位图片的格式 : http://via.placeholder.com/width x height /ffffff/00000/
, width与height分别代表图片的宽高,fffff是图片的背景颜色,000000是图片的文本颜色。
随机生成图片的大小(宽高限定一个范围),背景颜色与文本颜色。这里添加一个参数,imgNum
:确定需要图片的数量。最后返回包含这些图片链接的一个数组
getImgUrls: function(imgNum){
let imgUrls = [];
let colorArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "A", "B", "C", "D", "E", "F"]; // 颜色数组[0-9, A-F],
// 该颜色数组生成与源代码稍有不同,在我的GitHub上是用for循环生成的
for(let i = 0; i < imgNum; i++) {
let imgWidth = Math.floor(Math.random()*50+50); // 设定宽度50-100
let imgHeight = Math.floor(Math.random()*30+50); // 设定高度为30-80
let bgColor = textColor = ""; // 下面使用的是字符串拼接,每次使用都需要重新清空
for(let j=0; j < 6; j++){
bgColor += colorArr[Math.floor(Math.random()*16)];
textColor += colorArr[Math.floor(Math.random()*16)];
}
let url = "http://via.placeholder.com/" + imgWidth + "x" + imgHeight + "/" + bgColor + "/" + textColor;
imgUrls.push(url);
}
return imgUrls;
}
loadImg() 方法:用来加载图片,图片来源就从已有素材中寻找。即getImgUrls()
在这个方法中,需要做的就是获取图片信息(宽高,src等)并改变大小,高度与行高一致,宽度等比例改变。并且将信息存储起来,等每张图片加载完毕后就加入渲染排列图片的队列中。
loadImg: function(imgNum){
let imgUrlsArr = this.getImgUrls(imgNum);
let _this = this; // 保存this指针的指向,方便调用属性及方法
for(let i = 0; i < imgNum; i++){
let newImg = new Image(); // 新建图片对象
newImg.src = imgUrlsArr[i]; // 加载图片内容
newImg.onload = function(){
// Image对象加载了src后拥有宽高属性, imgInfo存储图片信息
let ratio = this.width / this.height;
let imgInfo = {
target: this, // 用来存放当前目标newImg,方便后续调用
height: _this.rowHeight,
width: ratio * _this.rowHeight, // 等比例缩放
ratio: ratio,
};
// 把加载完的图片加入渲染队列
_this.render(imgInfo);
}
}
},
render()方法:渲染队列的方法,主要是判断图片能否放在一行上(每次把图片加入到imgArr队列中就可判断长度),并当图片符合占满一行的条件时,将最后一张图片放到下一行,并记录需要改变的图片比例交由layout()
方法更改。
render: function(imgInfo){
// 定义该行图片宽度之和
let wholeWidth = 0;
this.imgArr.push(imgInfo);
for(let i = 0; i < this.imgArr.length; i++){
wholeWidth += this.imgArr[i].width;
}
// 如果该行加入的图片宽度大于了该行的宽度
// 就需要弹出最后一张图片,并更改前面的图片大小比例
if(wholeWidth > this.width){
let lastImg = this.imgArr.pop();
wholeWidth -= lastImg.width;
// 利用面积相等原则,来计算新的高度
let newHeight = this.width * this.rowHeight / wholeWidth;
this.layout(newHeight);
// 放置完毕之前的图片之后,清空该图片队列
// 并将上一行溢出的图片 作为下一行的第一张
this.imgArr = [];
this.imgArr.push(lastImg);
}
}
layout()方法:获得newHeight
参数,是图片的新高度,改变该行图片的高度,使得这些图片自适应改变宽度之后能占满该行。也就是说这个木桶布局的高度会发生变化 与之前设定的this.rowHeight
相近但不相等。
之后创建节点,并把修改好的图片依次加入到创建好的节点上,然后添加到页面中。
layout: function(newHeight){
// 一次只放一行, 所以只生成一个imgRow
let imgRow = document.createElement("div");
imgRow.classList.add("img-row");
// 一行包含若干个图片,所以需要若干个imgBox,并将图片加入其中
for(let i = 0; i < this.imgArr.length; i++){
let imgBox = document.createElement("div");
imgBox.classList.add("img-box");
let img = this.imgArr[i].target;
// 改变了高度之后宽度自己会跟着改变
img.style.height = newHeight + "px"; // 注意加"px"
imgBox.appendChild(img);
imgRow.appendChild(imgBox);
}
// 先把图片加载到图片盒子里,然后加到图片列中,最后加到容器中
this.ct.appendChild(imgRow);
}
最后便是两行代码来运行这段程序
let ct = document.querySelector(".ct");
let barrel = new Barrel(ct, 100, 100); // 100张图片数量, 指定每行的初始行高为100