木桶布局 实现

百度图片

图片来自 百度图片

像这样高度一样,而宽度不同的布局方式称之为木桶布局。它有几个鲜明的特点: 每行的图片高度一致;每行的图片都是占满的。

如何实现木桶布局 之 整体思路

我们需要先拥有一些素材(图片), 并且将这些图片横向摆放到页面上。假如我现在有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 

效果预览及代码地址

效果预览
代码地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容