JavaScript、jQuery和CSS3实现瀑布流布局

html结构

(Emmet)
(div.box>div.pic>img[src="images/$.jpg"])*23

<div id="main">
        <div class="box">
            <div class="pic">![](images/1.jpg)</div>
        </div>
        <div class="box">
            <div class="pic">![](images/2.jpg)</div>
        </div>
        <div class="box">
            <div class="pic">![](images/3.jpg)</div>
        </div>
        <div class="box">
            <div class="pic">![](images/4.jpg)</div>
        </div>
</div>

css结构

*{
    margin:0;
    padding:0;
}
#main{
    position: relative;
    
}
.box{
    padding:10px 0 0 15px;
    /*display: inline-block;一行显示*/
    float: left;
}
.pic{
    padding: 10px;
    border:1px solid #ccc;
    border-radius: 5px;
    box-shadow: 0 0 5px #ccc;
    
}
.pic img{
    /*瀑布流特点*/
    width: 165px;
    height: auto;
}

一、JavaScript原生方法实现瀑布流布局

整个功能封装在waterfall()函数中。

图片定位

js文件是在head中引入,所以执行的脚本需要放在window.onload事件中。
因为我们进行操作的是main下的box元素,首先要先进行获取元素。
然鹅,js中没有提供专门获取Class名的元素的方法。又为了方面后面的函数的调用,我们将获取父元素main下的所有class为box的子元素的这个功能进行封装getByClass()

window.onload = function () {
      waterfall('main','box');
}
 function waterfall(parent,box){
    //把父元素main下所有class名为box的子元素都取出来
    var oParent = document.getElementById(parent);
    getByClass(oParent,box);
}

getByClass()实现的思路:若要获取父元素下所有class为特定名的子元素,首先要把父元素下所有子元素全部都取出来,然后进行遍历,判断每个子元素上的className是否和你传入的class名相等;如果相等,这个子元素就是我们要找的,然后把这些我们要找的子元素存储起来boxArr
这里,通过Class取到的所有元素,最后的结果是数组类型(不止一个)。
获取到的元素要存储起来,boxArr.push()向数组的末尾添加元素。

//根据class获取元素
function getByClass(parent,clsName){
    var boxArr = new Array(),//用来存储获取到的所有class为box的元素
        oElements = parent.getElementsByTagName('*');//取出父元素下的所有子元素

    for (var i = 0; i < oElements.length; i++) {
        if(oElements[i].className == clsName){
            boxArr.push(oElements[i]);
        }
    }
    return boxArr;
}

getByClass()函数最后返回的是一个数组,var oBoxs = getByClass(oParent,box);声明一个变量oBoxs来接收获取到的所有元素。console.log(oBoxs.length);// 23 说明已经全部取出
这样就把ID为main的父元素下所有class为box的子元素都取出来了。

问题:

oElements[i].className == className ,用这种方法判断,欠妥当。

因为现实项目中,className 不止一个,这样就永远没法相等,应该这样判断

oElements[i].className.indexOf(className) > 0

--
功能点
浏览器窗口大小变化时,页面中一行里图片的个数是固定的。即图片列数不随浏览器窗口大小的变化而变化。

思路:图片列数是固定值→使大盒子main的宽度值固定即可→图片的列数×一个box的宽

3个步骤:

  1. 确定列数:以当前的页面宽度,除以一个 box 的宽度,结果取整{Math.floor()}

  2. 确定 main 容器的宽度:列数(即每行中能容纳box的个数)乘以一个 box 的宽(也可以这样写:oParent.style.width = oBoxW*cols+'px';)

  3. 定位第一行盒子:将 box 集合作为数组取出,遍历子元素,加入入数组

一个box的宽=图片宽度165+内边距10×2+边框1×2 +填充15
waterfall()中执行以下代码,设置main的宽度以及对齐方式。使用cssText属性以字符串的形式对其设置。offsetWidth计算的是没有外边距的盒子宽。

//计算整个页面显示的列数(页面宽/box的宽)
    var oBoxW = oBoxs[0].offsetWidth;//等宽
    // console.log(oBoxW);
    var cols = Math.floor(document.documentElement.clientWidth / oBoxW)//取整;
    // console.log(cols);
    //设置main的宽
    oParent.style.cssText = 'width:'+oBoxW*cols+'px;margin:0 auto;'

图片排序(盒子排列)

3个步骤:

  1. 找到上一行里高度最小的盒子(即空隙最大的地方)

  2. 把要排列队列里的第一个的盒子定位到这个空白处

(需要两个数值,第一个是上一行最矮盒子的高度【方法:Math.min.apply()】,第二个是上一行最矮盒子的左边距【两种办法:盒子宽最矮盒子下标;数组里最小盒子的offsetLeft。】) *

  1. 更新这一列的高度,最矮元素的高,加上当前盒子的高度

--
arrayObject.push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。
Math.min()返回的是一组数据中的最小值。
Math.min.apply(null,hArr)取数组中的最小值;apply()接收两个参数,一个是函数运行的作用域(this),第一个参数是null的情况下,this指向window,另一个是参数数组。

--
求最矮盒子的下标这里写出getMinhIndex(hArr,minH)函数,或者使用hArr.indexOf(minH)直接返回下标值。

getMinhIndex(hArr,minH)这里一旦找到这个最小值,就返回了索引号;若有多个最小高度值相等,那么返回的是这个队列中第一个出现的最小高度值。

//遍历数组中的每一个值,若与传入的特定值相等,返回该下标值。
function getMinhIndex(arr,val){
        for(var i in arr){
            if(arr[i]==val){
                return i;
            }
        }
    }

这样就找到了上一行最矮的那个盒子以及该盒子所在的索引号。接下来就对下一行第一个盒子进行绝对定位。这时候发现剩下的所有盒子重叠了。这是因为上一行所有图片的高度hArr[],是固定的那几个值,所以求得的最小值minH是固定的。即后面的所有盒子都堆在了这个固定最矮图片的下面。

解决:修改数组hArr[]里的高度值→改变最小高度值hArr[Index] += oBoxs[i].offsetHeight。最矮元素的高,加上当前盒子的高度,更新这一列的高度。


//waterfall()函数中执行。
//先把上一行图片的高度全都取出来,然后进行存储。再找出图片高度的最小值以及该图片所在的索引对下一个盒子进行绝对定位。

var hArr = [];//存放每一列高度的数组
    for (var i = 0; i < oBoxs.length; i++) {
        if (i<cols) {
            //或hArr[i]=oBoxs[i].offsetHeight; 
            hArr.push(oBoxs[i].offsetHeight);    //获取第一行盒子的高度并进行存放
        }else{
             //下一行里第一张图片的定位
            var minH = Math.min.apply(null,hArr);//获取最小值// console.log(minH);
            var Index = getMinhIndex(hArr,minH);//获取索引值
              //var Index = hArr.indexOf(minH);
            oBoxs[i].style.position = 'absolute';
            oBoxs[i].style.top = minH + 'px';//把图片加上一行中最矮的图片的底下
            oBoxs[i].style.left = oBoxs[Index].offsetLeft + 'px';//px
            //修改hArr中的最小值
            hArr[Index] += oBoxs[i].offsetHeight; 

        }
        console.log(hArr);
    }

图片加载功能

思路:

  1. 何时加载:滚动条x向上滚动的距离(scrollTop)与可视区页面高度(clientHeight)之和 大于最后一张图片的距离父元素顶端位置(offsetTop)与盒子高度(**offsetHeight **)的一半之和。
    offsetTop+offsetHeight / 2是固定值,滚动条向下滚动的距离和页面向上偏离的距离相等,知道滚到当前队列中最后一张图片自身高度的一半或者该图片刚显露出来(自定义)开始加载其他的图片。滚动条向下滚动的距离和页面的可视区高度若小于这个固定值,说明没有滚到需要加载图片的时候。
  2. 怎么加载:json数据交换格式;创造元素并进行嵌套(appendChild()方法 语法:parent.appendChild(children))将数据信息渲染到页面中。
    由于数据都是从后台来,这里模拟json格式的数据;首先进行遍历,然后创建盒子,塞到main盒子里。遍历给出的数据,将图片添加到数据块中渲染出来

首先实现何时加载功能checkScrollSlide().找出当前队列中的最后一个盒子oBoxs[oBoxs.length - 1],计算数值时一般计算机能接受的最小单位是像素,即整数,所以求盒子自身高度值的一半用到Math.floor()

返回的是布尔型。是否加载。

//监测是否具备滚动加载数据块的条件
function checkScrollSlide(){
        var oParent = document.getElementById('main');
        var oBoxs = getByClass(oParent,'box');
        var lastBoxH = oBoxs[oBoxs.length - 1].offsetTop + Math.floor(oBoxs[oBoxs.length - 1].offsetHeight / 2);
        // console.log(lastBoxH);最后一个盒子到页面顶部的距离+自身高度的一半
        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;//滚动条向上滚动的距离
        // console.log(scrollTop);
        var height = document.body.clientHeight || document.documentElement.clientHeight;//页面可视区高度
        // console.log(height);
        return (scrollTop+height>lastBoxH)?true:false;//三元操作符
        // return scrollTop+height>lastBoxH;
    }

用到window.onscroll()滚动条滚动事件,在页面加载完毕后即window.onload()里触发。
dataInt是一个对象,以数组的形式存放数据信息。data为数据属性。dataInt.data.length数据的个数。
数据加载进来后,只是这些要渲染的数据只是被加到页面中,并没有进行图片定位和图片排序。会出现重叠和图片间有空白等现象。这时,需要再调用一下waterfall()函数。

这样,页面中滚动条不断下拉时,dataInt对象里几张图片就会不断的进行加载。

var dataInt = {"data":[{"src":'0.jpg'},{"src":'1.jpg'},{"src":'2.jpg'},{"src":'3.jpg'}]};//模拟后台数据
window.onscroll = function () {
        if (checkScrollSlide) {//为真
            //将数据块渲染到页面的尾部
            var oParent = document.getElementById('main');
            //遍历数据块
            for(var i=0;i<dataInt.data.length;i++){
                //创建存放数据块的盒子并渲染到页面中
                var oBox = document.createElement('div');
                oBox.className = 'box';
                oParent.appendChild(oBox);

                var oPic = document.createElement('div');
                oPic.className ='pic';
                oBox.appendChild(oPic);

                var oImg = document.createElement('img');
                //获取数据块中的文件名
                oImg.src="images/"+dataInt.data[i].src;//都存放在固定文件images里
                oPic.appendChild(oImg);

            }
            waterfall('main','box');
            
        } 
    }

问题:
checkScrollSlide调用的问题
函数只要是要调用它进行执行的,都必须加括号。此时,函数()实际上等于函数的返回值。当然,有些没有返回值,但已经执行了函数体内的行为,这个是根本,就是说,只要加括号的,就代表将会执行函数体代码。

不加括号的,都是把函数名称作为函数的指针,用于传参,此时不是得到函数的结果,因为不会运行函数体代码。它只是传递了函数体所在的地址位置,在需要的时候好找到函数体去执行。

二、JQuery实现瀑布流布局

$(window).on('load',function(){
    waterfall();
    var dataInt = {"data":[{"src":'0.jpg'},{"src":'1.jpg'},{"src":'2.jpg'},{"src":'3.jpg'}]};
    $(window).on('scroll',function(){
        if(checkScrollSlide){
             //创建盒子并添加到页面中
            $.each(dataInt.data,function(key,value){
                var oBox=$('<div>').addClass('box').appendTo($('#main'));
                var oPic=$('<div>').addClass('pic').appendTo($(oBox));
                // console.log(value);value是dataInt里的对象,即原生js对象,需加$装换成JQuery对象才能使用JQuery方法
                var oImg=$('<img>').attr('src','images/'+$(value).attr('src')).appendTo($(oPic));
            })
            waterfall();
        }
    })
});

function waterfall(){
    var $boxs=$('#main>div');//获取main下的一级div元素
    var w = $boxs.eq(0).outerWidth();//一个盒子的宽度包括填充和边框
    var cols = Math.floor($(window).width()/w);
    $('#main').width(w*cols).css('margin','0 auto');//设置main的宽度以及对齐方式
    
    var hArr = [];
   //数组遍历
    $boxs.each(function(index,value){
        // console.log(index);
        // console.log(value);DOM对象
        var h =$boxs.eq(index).outerHeight();
        if(index<cols){
            hArr[index]=h;
        }else{
            var minH = Math.min.apply(null,hArr);//最小值
            var minHIndex = $.inArray(minH,hArr);//索引
            //console.log(value);value是DOM对象,需加$装换成JQuery对象才能使用JQuery方法
            $(value).css({
                'position':'absolute',
                'top':minH+'px',
                'left':minHIndex*w+'px'
            })
            hArr[minHIndex]+=$boxs.eq(index).outerHeight();//更新数组
        }

    })
    // console.log(hArr);
}

function checkScrollSlide(){
    var $lastBox=$('#main>div').last();//获取最后一个元素
    var lastBoxDis=$lastBox.offset().top+Math.floor($lastBox.outerHeight()/2);
    var scrollTop=$(window).scrollTop();//滚动条滚动的距离
    var documentH=$(window).height();//页面可视区高度
    return (lastBoxDis<scrollTop+documentH)?true:false;
}

三、CSS3实现瀑布流布局

根据盒子的宽度设置column-width属性。这里一个box的宽=图片宽度165+内边距10×2+边框1×2 +填充15
这种方式不需要计算,只需要设置列宽,浏览器自动计算,性能高。但是列宽会随着浏览器窗口的大小进行改变,用户体验不好;另外图片排序是按照垂直顺序排列的。最后图片的加载需要JavaScript实现。

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

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,725评论 1 92
  • html结构 (Emmet) (div.box>div.pic>img[src="images/$.jpg"])*...
    LaBaby_阅读 632评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 因为手机的缘故,现在的人都成了家里蹲,没有机会看见外面的光景,其实只有当你踏出家门,你才会知道玩手机是多么的无聊,...
    夏日熏风阅读 226评论 4 2
  • 【七月影语】20170819学习力践行Day90 1.复习已会古诗,背新古诗 2.读故事 3.听鹅妈妈 4.游戏贴纸书
    暖小柒阅读 115评论 0 0