html結構
(Emmet)
(div.box>div.pic>img[src="images/$.jpg"])*23
<div id="main">
<div class="box">
<div class="pic"></div>
</div>
<div class="box">
<div class="pic"></div>
</div>
<div class="box">
<div class="pic"></div>
</div>
<div class="box">
<div class="pic"></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個步驟:
確定列數:以當前的頁面寬度,除以一個 box 的寬度,結果取整{Math.floor()}
確定 main 容器的寬度:列數(即每行中能容納box的個數)乘以一個 box 的寬(也可以這樣寫:oParent.style.width = oBoxW*cols+'px';)
定位第一行盒子:將 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個步驟:
找到上一行里高度最小的盒子(即空隙最大的地方)
把要排列隊列里的第一個的盒子定位到這個空白處
(需要兩個數值,第一個是上一行最矮盒子的高度【方法:Math.min.apply()】,第二個是上一行最矮盒子的左邊距【兩種辦法:盒子寬最矮盒子下標;數組里最小盒子的offsetLeft。】) *
- 更新這一列的高度,最矮元素的高,加上當前盒子的高度
--
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);
}
圖片加載功能
思路:
- 何時加載:滾動條x向上滾動的距離(scrollTop)與可視區頁面高度(clientHeight)之和 大于最后一張圖片的距離父元素頂端位置(offsetTop)與盒子高度(**offsetHeight **)的一半之和。
offsetTop+offsetHeight / 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;
}