waterfall瀑布流布局+動(dòng)態(tài)渲染的實(shí)現(xiàn)
瀑布流典型網(wǎng)站
花瓣網(wǎng)、堆糖
瀑布流布局原理
大體思路
首先先是頁(yè)面布局 特點(diǎn)是:寬度一樣,長(zhǎng)度不一樣

由此可以知道,這種布局要用到 絕對(duì)定位 的思想來(lái)做。
上面的五個(gè)正常排列,到了第六個(gè)以后就要找最矮的追加了。
如何獲取最矮的一列呢?
第一個(gè)最好找,其他的每一個(gè)盒子可以獲取它的高度,找最矮的盒子,然后找到最矮盒子的定位。
新追加進(jìn)去的盒子的定位是:
- left:最矮盒子的索引*(盒子的寬度+左右間距)
- top: 這個(gè)盒子的高度 + 上下間距
放進(jìn)去之后這一列的高度變化,記錄下來(lái)生成新的高度,然后進(jìn)行下一輪高度的比較。以此類推。
waterful 是一個(gè)組件,基于 jquery 的一個(gè)組件。
具體思路

最外邊的左右兩邊是沒(méi)有間距的,所以 5 列的情況下有 4 個(gè)間距。所以寬度 width 一定的情況下,間距的寬度 space 是可以計(jì)算出來(lái)的:
間距
var space = (wParent - 5 * width) / (col - 1); // wParent 父盒子的寬度,width是子盒子的寬度,col是列數(shù)
第一排的盒子的定位:
- top : 0
- left : 索引*(width + space)
第二排的盒子的定位:
- top : minHeight + space
- left : 索引*(width + space)
所以 5 列的高度要用一個(gè)數(shù)組表示,可以找到最矮的元素以及其當(dāng)前的索引。
插件封裝
因?yàn)樵诘谝淮渭虞d和第 n 次加載的時(shí)候,都要進(jìn)行瀑布流布局。所以將瀑布流布局的方法進(jìn)行一個(gè)插件進(jìn)行封裝,可以形成代碼的復(fù)用。 首先了解瀑布流的 html 布局
<!--頁(yè)面容器-->
<div class = "container">
<!--所有item的集合,距離頂部有距離-->
<div class = "items">
<!--每一個(gè)小塊,包含了圖片和文字-->
<div class = "item">
<img src = "" />
<p>hello</p>
</div>
<div class = "item">
<img src = "" />
<p>hello</p>
</div>
</div>
</div>
<div class = "btn">正在加載...</div>
下面就來(lái)封裝一個(gè) jquery 的插件
第一步
將 jquery 中的全局變量轉(zhuǎn)化為局部變量。
防止全局污染,提高性能 形成一個(gè)閉包,閉包里面定義的變量是不會(huì)影響外部變量的。
/*自調(diào)用 形成一個(gè)閉包 */
(function($){
/*如果不加jQuery里面使用的$是全局變量,加了之后使用的就是成員變量*/
})(jQuery);
第二步
jquery.fn.extend(object)
jquery 中的 fn 函數(shù)
提供一個(gè)第三方方法的一個(gè)入口,擴(kuò)展 jquery 元素集(使用 $ 可以獲取到的元素) 來(lái)提供新的方法(通常用來(lái)制作插件)
/*js/jquery-waterfall.js*/
(function($){
$.fn.waterfall = function(){
/*this指向的是當(dāng)前調(diào)用這個(gè)方法的元素集(元素集是jquery獲取元素是一個(gè)偽數(shù)組)*/
console.log(this);
}
})(jQuery);
第三步
對(duì)第一排進(jìn)行排列
(function($){
$.fn.waterfall = function(){
// this指向的是當(dāng)前調(diào)用這個(gè)方法的元素集
// 當(dāng)前的瀑布流父容器
var items = $(this);
//父容器的寬度
var wParent = items.width();
//當(dāng)前的瀑布流子容器
var child = items.children();
//獲取子容器的寬度
var width = child.width();
//假設(shè)排多少列
var col = 5;
//計(jì)算間距(父元素的寬度減所有盒子的寬度/4)
var space = (wParent - col * width) / (col - 1);
//記錄每列高度的數(shù)組
var colHeightArr = [];
//遍歷每一個(gè)子元素
$.each(child,function(i,item){
var $item = $(item);
var height = $item.height();
//設(shè)置定位
//第一排的元素都是靠頂部的,所以索引從0開始,小于5的時(shí)候都是靠頂部的
if(i < col ){
$item.css({
top: 0,
left:i * (width + space)
});
//把高度添加進(jìn)數(shù)組中
colHeightArr[i] = height;
//也可以用 colHeightArr.push(height);
}
//其他的都要根據(jù)最矮的一列進(jìn)行排列
});
}
})(jQuery);
這樣你就看到了效果圖(因?yàn)槟M做了 13 個(gè)盒子,所以剩下的疊在了一起)

這個(gè)時(shí)候打印以下高度數(shù)組:

可以看到前 5 個(gè)的高度都存到數(shù)組中去了??梢耘袛喑鰜?lái)數(shù)組中最小的是 289 , 289 對(duì)應(yīng)的數(shù)組的索引就是那一列的索引。
第四步
對(duì)其余的排進(jìn)行排列。找最小的追加,然后本列的高度增加。以此類推。 最大的 items 等于最大的高度。這樣才可以把下面的加載移動(dòng)到下邊去。
(function($){
$.fn.waterfall = function(){
// this指向的是當(dāng)前調(diào)用這個(gè)方法的元素集
// 當(dāng)前的瀑布流父容器
var items = $(this);
//父容器的寬度
var wParent = items.width();
//當(dāng)前的瀑布流子容器
var child = items.children();
//獲取子容器的寬度
var width = child.width();
//假設(shè)排多少列
var col = 5;
//計(jì)算間距
var space = (wParent - col * width) / (col - 1);
//記錄每列高度的數(shù)組
var colHeightArr = [];
//遍歷每一個(gè)子元素
$.each(child,function(i,item){
var $item = $(item);
var height = $item.height();
//定位
//第一排的元素都是靠頂部的
//索引從0開始,小于5的時(shí)候都是靠頂部的
if(i < col ){
$item.css({
top: 0,
left:i * (width + space)
});
//colHeightArr[i] = height;
colHeightArr.push(height);
//其他的都要根據(jù)最矮的一列進(jìn)行排列
}else{
//找到最矮的那一列進(jìn)行排列
//索引
var index = 0;
//假設(shè)最小的高度是第一個(gè)索引對(duì)應(yīng)的高度
var minHeight = colHeightArr[index];
//遍歷數(shù)組,找到最小值和最小值對(duì)應(yīng)的索引
//k是索引,v是值
$.each(colHeightArr,function(k,v){
if(minHeight > v){
index = k;
minHeight = v;
}
});
//定位
$item.css({
top:minHeight + space,
left:index * (width + space)
})
//當(dāng)前數(shù)組中最小的高度進(jìn)行新的高度的更新
colHeightArr[index] = minHeight + space + height;
}
//console.log(colHeightArr);
});
//設(shè)置父容器的高度
var maxHeight = colHeightArr[0];
$.each(colHeightArr,function(k,v){
if(maxHeight < v){
maxHeight = v;
}
});
//給父容器設(shè)置最高的高度
items.height(maxHeight);
}
})(jQuery);
效果圖:

第五步
html 中調(diào)用(上面的效果圖都是已經(jīng)調(diào)用過(guò)的)
$(".items").waterfall();
但是如果有圖片的話,這樣調(diào)用在網(wǎng)絡(luò)比較慢的情況下會(huì)出現(xiàn)問(wèn)題。在圖片沒(méi)有加載出來(lái)的時(shí)候排列,中間圖片加載完畢會(huì)造成盒子重疊的效果。
解決辦法:
/*頁(yè)面上所有的資源都加載完成后進(jìn)行布局,否則獲取不到圖片的尺寸撐不開盒子的高度*/
window.onload = function(){
$(".items").waterfall();
}
//為什么不用jquery的,因?yàn)檫@個(gè)是在dom元素下載完畢之后進(jìn)行加載這個(gè)方法,需要等所有的資源加載完之后進(jìn)行排列
/*
$(function(){
//console.log('dom loaded');
});
*/
動(dòng)態(tài)渲染
因?yàn)閿?shù)據(jù)很多,所以會(huì)進(jìn)行分批次渲染。
原理圖:

接口文檔:
接口說(shuō)明: 瀑布流分頁(yè)數(shù)據(jù)
接口地址:data.php
請(qǐng)求方式:get
接口參數(shù):page 當(dāng)前是第幾頁(yè)
pageSize 當(dāng)前頁(yè)要顯示多少條
返回類型:json
返回?cái)?shù)據(jù):
{ page:2,items:[{path:"./images/1.jpg",text:'''},...] }
page 下一頁(yè)的頁(yè)碼(根據(jù)頁(yè)碼獲取下一頁(yè)的數(shù)據(jù))
items 返回當(dāng)前頁(yè)的數(shù)據(jù)
path 圖片地址
text 文字
此時(shí)我們要準(zhǔn)備好殼子
<div class="container">
<div class="items">
<!--TODO 需要渲染數(shù)據(jù)的地方-->
</div>
<div class="btn loading">正在加載中...</div>
</div>
需求分析
加載第一頁(yè)的時(shí)候
1.加載第一頁(yè)的數(shù)據(jù) ajax
2.按鈕需要顯示成加載更多
3.加載完成渲染到頁(yè)面當(dāng)中 artTemplate
4.初始化成瀑布流布局 waterfall
加載下一頁(yè)的時(shí)候
1.加載數(shù)據(jù)
- 手動(dòng)加載:點(diǎn)擊按鈕加載下一頁(yè)的數(shù)據(jù)
- 自動(dòng)加載:滾動(dòng)到底部的時(shí)候主動(dòng)加載下一頁(yè)
2.按鈕需要顯示 “正在加載中...” 不能點(diǎn)擊 防止重復(fù)提交
3.加載完成渲染到頁(yè)面當(dāng)中
4.初始化成瀑布流布局
5.按鈕需要顯示成加載更多
沒(méi)有更多數(shù)據(jù)把按鈕禁用 顯示 “沒(méi)有更多數(shù)據(jù)了”
渲染第一頁(yè)數(shù)據(jù)
發(fā)送請(qǐng)求
既然加載頁(yè)面的時(shí)候都會(huì)用到加載數(shù)據(jù)、渲染頁(yè)面、初始化瀑布流,就把這三個(gè)動(dòng)能封裝到一個(gè)函數(shù)中去,先實(shí)現(xiàn)第一個(gè)功能:
$(function(){
//實(shí)現(xiàn)動(dòng)態(tài)的瀑布流渲染
//渲染
var render = function(){
// 加載數(shù)據(jù) 渲染頁(yè)面 瀑布流布局
$.ajax({
type:'get',
url:'data.php',
data:{
//第一頁(yè)
page:1,
//每頁(yè)10條
pageSize:10
},
dataType:'json',
success:function(data){
console.log(data);
}
});
}
render();
});
拿到的數(shù)據(jù)如圖:

渲染頁(yè)面
準(zhǔn)備模板
<script type="text/template" id="template">
<% for(var i=0 ; i<items.length ; i++){ %>
<div class="item">
<img src="<%=items[i].path%>" alt="">
<p><%=items[i].text%></p>
</div>
<% } %>
</script>
<script>
$(function(){
//獲取需要操作的dom
var $items = $(".items");
var $btn = $(".btn");
//渲染
var render = function(){
// 加載數(shù)據(jù) 渲染頁(yè)面 瀑布流布局
$.ajax({
type:'get',
url:'data.php',
data:{
page:1,
pageSize:10
},
dataType:'json',
success:function(data){
console.log(data);
$items.append(template('template',data));
//瀑布流布局
$items.waterfall();
//更改按鈕
$btn.removeClass('loading').html('加載更多');
}
});
}
render();
});
</script>
第二頁(yè)面的渲染(手動(dòng)加載)
第二頁(yè)需要改變的東西:
- 添加按鈕的點(diǎn)擊事件,點(diǎn)擊按鈕之后就進(jìn)行渲染。
- 點(diǎn)擊按鈕加載的時(shí)候,要給按鈕加鎖,因?yàn)椴患拥脑挄?huì)發(fā)送多個(gè)ajax請(qǐng)求,判斷按鈕是不是loading狀態(tài),如果是的話就不渲染數(shù)據(jù)。
- render函數(shù)中,在進(jìn)行按鈕狀態(tài)改變的時(shí)候,用自定義屬性記錄下來(lái)下一頁(yè)的要獲取的頁(yè)數(shù)。利用data(),里面?zhèn)饕粋€(gè)page,把data.page放進(jìn)去。所以在拿數(shù)據(jù)的時(shí)候,要從按鈕的data中獲取page的值。第一次是空的,所以就設(shè)定一個(gè)默認(rèn)值為1
- render函數(shù)中在數(shù)據(jù)成功加載之前,按鈕還是loading狀態(tài),所以加一個(gè)beforeSend的函數(shù),里面是loading狀態(tài)。
- render函數(shù)中在渲染的時(shí)候判斷一下是不是沒(méi)有數(shù)據(jù)了,根據(jù)返回的數(shù)組中的長(zhǎng)度是不是為零來(lái)判斷,如果是零的話就顯示沒(méi)有更多數(shù)據(jù)了。
$(function(){
//獲取需要操作的dom
var $items = $(".items");
var $btn = $(".btn");
//渲染
var render = function(){
// 加載數(shù)據(jù) 渲染頁(yè)面 瀑布流布局
$.ajax({
type:'get',
url:'data.php',
data:{
//取下一頁(yè)的頁(yè)碼,沒(méi)有的話就默認(rèn)是1
page:$btn.data("page")||1,
//每頁(yè)10條
pageSize:10
},
beforeSend:function(){
$btn.addClass("loading").html('正在加載中...');
},
dataType:'json',
success:function(data){
console.log(data);
//準(zhǔn)備模板
//因?yàn)槭亲芳铀圆荒苡胔tml,要用append
//直接用data的原因是因?yàn)閐ata本來(lái)就是一個(gè)對(duì)象,里面有很多的屬性,而不是一個(gè)數(shù)組,數(shù)組的話不能這樣,因?yàn)閿?shù)據(jù)只有l(wèi)ength一個(gè)屬性
$items.append(template('template',data));
//瀑布流布局
$items.waterfall();
if(data.items.length){
//更改按鈕
//data是一個(gè)自定義屬性,把數(shù)據(jù)中傳輸出來(lái)的page保存在自定義屬性當(dāng)中,
$btn.data("page",data.page).removeClass('loading').html('加載更多');
}else{
//沒(méi)有更多數(shù)據(jù)
//判斷什么時(shí)候就沒(méi)有數(shù)據(jù)了,打開最后的一個(gè)對(duì)象,里面items的數(shù)組的長(zhǎng)度為零
$btn.addClass("loading").html("沒(méi)有更多數(shù)據(jù)了");
}
}
});
}
//按鈕加載
$btn.on('click',function(){
//避免發(fā)送多個(gè)ajax請(qǐng)求,就進(jìn)行判斷,如果是loading狀態(tài),就退出,
if($btn.hasClass("loading")){
return false;
}
render();
})
render();
});
第二頁(yè)面的渲染(滾動(dòng)加載)
說(shuō)到滾動(dòng)渲染,就是要我們渲染過(guò)的頁(yè)面到瀏覽器底部的一定距離就要進(jìn)行下一次的請(qǐng)求了,這就要進(jìn)行一個(gè)判斷了。
原理圖:

當(dāng) bottom < 200px 的時(shí)候進(jìn)行 ajax 請(qǐng)求。
其中 bottom 要怎么計(jì)算?
bottom = items的高度 + items距離頂部的距離 - 向上卷曲的高度 - 整個(gè)瀏覽器的高度
$(function(){
//實(shí)現(xiàn)動(dòng)態(tài)的瀑布流渲染
//獲取需要操作的dom
var $items = $(".items");
var $btn = $(".btn");
//渲染
var render = function(){
// 加載數(shù)據(jù) 渲染頁(yè)面 瀑布流布局
$.ajax({
type:'get',
url:'data.php',
data:{
page:$btn.data("page")||1,
pageSize:10
},
beforeSend:function(){
$btn.addClass("loading").html('正在加載中...');
},
dataType:'json',
success:function(data){
console.log(data);
$items.append(template('template',data));
//瀑布流布局
$items.waterfall();
//判斷數(shù)組中有沒(méi)有數(shù)據(jù)
if(data.items.length){
$btn.data("page",data.page).removeClass("loading").html('加載更多');
}else{
$btn.addClass("loading").html("沒(méi)有更多數(shù)據(jù)了");
}
}
});
}
//滾動(dòng)加載
$(window).on('scroll',function(){
//文檔距離底部的距離小于200px 去加載
//并且加載完成才能繼續(xù)加載
//items的高度
var itemsHeight = $items.height();
//items距離頂部的偏移量
var itemsTop = $items.offset().top;
//整個(gè)頁(yè)面距離頂部的卷曲出去的距離
var scrollTop = $(document).scrollTop();
// 瀏覽器的高度
var winHeight = $(window).height();
// 瀏覽器底部距離items底部的距離
var bottom = itemsHeight + itemsTop - scrollTop -winHeight;
// 判斷按鈕是不是loading狀態(tài)
var loading = $btn.hasClass("loading");
//如果按鈕小于200 且 不是loading狀態(tài)就開始加載
if(bottom < 200 && !loading){
render();
}
})
render();
});
需要特別注意的問(wèn)題
之前我們?cè)陟o態(tài)加載頁(yè)面的時(shí)候使用的是 window.onload ,是為了讓頁(yè)面上的資源全部加載完成之后再進(jìn)行頁(yè)面的渲染。否則就會(huì)產(chǎn)生頁(yè)面重疊的現(xiàn)象。
在動(dòng)態(tài)加載頁(yè)面的時(shí)候,我們先拿到后臺(tái)的數(shù)據(jù),然后轉(zhuǎn)化成 html 追加到頁(yè)面上之后,才開始加載 img 圖片。這里也遇到了之前的問(wèn)題。
之所以后面沒(méi)有用 window.onload 那樣的做,是因?yàn)樵驹O(shè)置的圖片已經(jīng)設(shè)定可寬和高。 img 有的設(shè)定了 250px ,有的設(shè)定了 450px 。 但是這樣做不合理,因?yàn)橛械膱D片會(huì)變形。
下面提供解決問(wèn)題的方法:
- 等所有的圖片加載完成進(jìn)行頁(yè)面渲染,但是這樣時(shí)間會(huì)比較長(zhǎng),不合理。
- 參考花瓣
花瓣在加載圖片的時(shí)候也進(jìn)行了寬高的設(shè)定,但是這個(gè)大小要根據(jù)原圖片的尺寸進(jìn)行大小的縮放。

原來(lái)的尺寸是 608 ,現(xiàn)在的寬度是 200 ,那么現(xiàn)在的高度就進(jìn)行一個(gè)換算
現(xiàn)在的高度 = 200 / 806 * 782
width 是現(xiàn)在的寬度
// 模板引擎中寫
<img height = "<%=items[i].width * items[i].height / width%>" src = "<%=items[i].path%>" />
/*
同樣在ajax的success中
$items.append(
template('template',{
data:data,
width:width
})
);
這樣width變量就可以使用了。
*/
這樣瀑布流就完成了.
到此這篇關(guān)于waterfall瀑布流布局+動(dòng)態(tài)渲染的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)waterfall瀑布流布局內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
這篇文章主要介紹了3種方式實(shí)現(xiàn)瀑布流布局小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)2019-09-05
詳解純css實(shí)現(xiàn)瀑布流(multi-column多列及flex布局)
這篇文章主要介紹了詳解純css實(shí)現(xiàn)瀑布流(multi-column多列及flex布局)的相關(guān)資料,用multi-column多列布局及flex布局實(shí)現(xiàn)瀑布流,感興趣的小伙伴們可以參考一下2018-09-04css3 column實(shí)現(xiàn)卡片瀑布流布局的示例代碼
這篇文章主要介紹了css3 column實(shí)現(xiàn)卡片瀑布流布局的示例代碼的相關(guān)資料,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-22- 這篇文章主要介紹了用CSS3實(shí)現(xiàn)瀑布流布局的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-10

CSS3實(shí)現(xiàn)瀑布流布局與無(wú)限加載圖片相冊(cè)的實(shí)例代碼
本篇文章主要介紹了CSS3實(shí)現(xiàn)瀑布流布局與無(wú)限加載圖片相冊(cè)的實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-12-22瀑布流布局的兩種實(shí)現(xiàn)方式:傳統(tǒng)多列浮動(dòng)和絕對(duì)定位布局
瀑布流布局想必大家對(duì)它并不陌生吧,在一些網(wǎng)站上都會(huì)有這種效果的出現(xiàn),下面為大家介紹下使用兩種方式實(shí)現(xiàn)傳統(tǒng)多列浮動(dòng)和絕對(duì)定位布局,具體的實(shí)現(xiàn)代碼如下,感興趣的朋友2013-08-26




