圖片來自 百度圖片
像這樣高度一樣,而寬度不同的布局方式稱之為木桶布局。它有幾個鮮明的特點: 每行的圖片高度一致;每行的圖片都是占滿的。
如何實現木桶布局 之 整體思路
我們需要先擁有一些素材(圖片), 并且將這些圖片橫向擺放到頁面上。假如我現在有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