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閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,776評論 1 92
  • html結構 (Emmet) (div.box>div.pic>img[src="images/$.jpg"])*...
    LaBaby_閱讀 654評論 0 0
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,635評論 25 708
  • 因為手機的緣故,現在的人都成了家里蹲,沒有機會看見外面的光景,其實只有當你踏出家門,你才會知道玩手機是多么的無聊,...
    夏日熏風閱讀 237評論 4 2
  • 【七月影語】20170819學習力踐行Day90 1.復習已會古詩,背新古詩 2.讀故事 3.聽鵝媽媽 4.游戲貼紙書
    暖小柒閱讀 123評論 0 0