基於jQuery的高可定製的瀑布佈局實現

其實瀑布佈局已經流行很久了,以前一直很喜歡,就是沒實現過。近日抽空研究了一下,做出了下面的實現。

上代碼前,先大概說一下原理:

1、inline-block做列布局實現多列

2、計算高度最低的列並隨即選擇其中一個作爲內容插入容器

3、通過延時和fadeIn實現動態效果

4、通過監聽滾動事件實現到底部的事件觸發以便靈活控制內容的加載與否

5、dom的處理完全交由程序來做,避免頁面還需要有配合實現的html代碼

6、通過error來監聽圖片異常,替換爲可顯示的默認圖片

 

更新了一下:

1、添加列數的調整支持,調整後自動重新繪製內容,用於適應窗口大小的改變(需自行實現監聽)

2、在每一次填充內容結束時判斷是否填滿頁面了,即如果未填滿,地步已經出現在畫面中了,則自動觸發加載下一頁事件

3、修復列數可以設置小於1的錯誤

 

先看一下最終效果:

 

其中灰色那塊是異常圖片替換的默認圖。

 

說明: 暫時沒去實現超長高度的隱藏處理。

 

ok,上代碼:

 

代碼有兩塊,一塊是樣式定義,即css,是使用該佈局時必須要用到的。還有一塊就是js的實現,js實現依賴jquery庫。

Javascript代碼:

/**
 * 瀑布佈局
 * @author tomtrije
 * @version 1.0.1
 * @last 2014/11/20 V1.0.1
 * @since 2014/11/20 V1.0.0
 * @update_list
 * 		V1.0.1
 * 		添加動態改變列數的支持(用於適應窗口大小改變),在改變列數的時候,對內容進行重新排布(高效率),但重新排佈會因內容的增多而效率減低。
 * 		在改變窗口大小後,可能一頁內容不足佔一屏,添加完成渲染後不足一屏自動觸發下一頁事件
 * 		V1.0.0
 * 		實現需要依賴jquery.falls.css樣式
 * 		支持定義列數和列寬
 * 		支持使用自定義的容器並支持容器內滾動事件監聽
 * 		支持自定義各元素樣式,但對此可能產生的錯誤不負責
 * 		支持圖片加載錯誤替換默認圖片
 * 		支持漸現時間和延時加載
 * 		支持自定義滾動事件響應,以便獲取下一頁內容
 * @format
 * 		加載數據格式約定如下:
 * 			[{src,href,text,title}]
 * 				src 圖片源地址
 * 				href 點擊圖片的跳轉鏈接(新窗口打開)
 * 				text 描述內容
 * 				title 圖片上的提示
 * 			所有屬性均可空,但爲空時會顯示空內容,所有屬性都爲空時跳過
 * -------------------------------------------------------------------
 * @desc 使用示例
 * -------------------------------------------------------------------
 			var falls = $("#container").falls({
	    		column: 4,
	    		columnWidth: 250,
				endText : 'The End',
				next : function(falls,page){
					//load page data into var "data"
					falls.load(data);
				}
			}).load(data);
			//重置列數
			falls.resetColumn(5);
 * -------------------------------------------------------------------
 * @param $ jQuery
 * @param window window對象
 * @param document document對象
 * @param undefined 用於忽略後續參數
 */
(function($, window, document, undefined) {
    var $window = $(window);
    //內容
	var content =   '<span class="jquery-falls-content" id="${id}">' +
						'<a target="_blank">' + 
							'<img />' +
						'</a>' +
						'<div class="jquery-falls-about"><p></p></div>' + 
					'</span>';
	/**
	 * 判斷對象是否爲空對象
	 * @param obj
	 * @returns boolean
	 */
	function isEmpty(obj){
	    for (var name in obj){
	        return false;
	    }
	    return true;
	}
	/**
	 * 獲取隨機數
	 * @param n 最大數
	 * @returns 隨機數
	 */
	function getRandom(n){
		return Math.floor(Math.random()*n+1)
	}
	/**
	 * 選擇插入列
	 * @param array 列容器數組
	 * @returns index 選擇的列序號
	 */
	function getIndex(array){
		//記錄每個列的高度和序號
		var lengthArray = new Array();
		for(var i = 0; i < array.length; i++){
			var obj = {};
			obj.len = array.eq(i).height();
			obj.index = i;
			lengthArray.push(obj);
		}
		//記錄最低高度的列
		var minIndexs = new Array();
		for(var i in lengthArray){
			var obj = lengthArray[i];
			if(minIndexs.length == 0){
				minIndexs.push(obj);
			}else{
				//如果有更低的列,清空緩存的最低高度的列容器數組,放入最新的列容器
				if(minIndexs[0].len > obj.len){
					minIndexs = new Array();
					minIndexs.push(obj);
				//如果有一樣低的列容器,放入數組
				}else if(minIndexs[0].len == obj.len){
					minIndexs.push(obj);
				}
			}
		}
		//如果只有一個最低列,直接選擇該列,否則從中隨機取一列
		if(minIndexs.length > 1){
			var random = getRandom(minIndexs.length - 1);
			return minIndexs[random].index;
		}else if(minIndexs.length == 0){
			return 0;
		}else{
			return minIndexs[0].index;
		}
	}
	/**
	 * 判斷內容是否還在容器視野底下
	 * 進入容器時觸發加載事件
	 * @param element 內容
	 * @param settings 參數
	 * @returns boolean true 還在容器視野底下 false 已進入容器視野
	 */
	$.belowthefold = function(element, settings) {
		$window = $(window);
        var fold;

        if (settings.container === undefined || settings.container === window) {
            fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop();
        } else {
            fold = $(settings.container).offset().top + $(settings.container).height();
        }
        //如果內容頂部減去預載高度大於容器底部,說明還在可以不用加載
        return fold <= $(element).offset().top - settings.threshold;
    };
	/**
	 * 定義瀑布佈局對象
	 * @param options 配置
	 * @returns 當前佈局對象
	 */
    $.fn.falls = function(options) {
    	var me = this;
    	//記錄當前頁數
    	var page = 0;
    	//默認配置
    	var settings = {
    		column : 6, //默認列數
    		columnWidth: 250,//默認列寬
    		parent : $(me),//整個瀑布佈局容器
    		fadeIn : 200,//漸現時間
    		delay : 50,//延遲加載時間
    		threshold : 0,//預載高度
    		container : window,//監聽滾動事件容器
    		endText : "End",//底部顯示內容
    		/**
    		 * 定義滾動到底部的事件
    		 * 可實現該函數處理加載並渲染下一頁內容
    		 * @param falls 瀑布佈局對象
    		 * @param page 當前頁
    		 */
    		next : function(falls,page){
    			falls.load([]);
    		},
    		//圖片爲空或加載異常顯示內容
    		empty : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
    	}
		//繼承配置
		if(options) {
			$.extend(settings, options);
			settings.column = settings.column < 1 ? 1 : settings.column;
		}
    	//創建頂級容器並賦予樣式
		var container = document.createElement("div");
		$(container).addClass("jquery-falls-container");
		if(settings.classes && settings.classes.fallsContainer){
			$(container).addClass(settings.classes.fallsContainer);
		}
		
		//創建主容器並賦予樣式
		var main = document.createElement("div");
		$(main).addClass("jquery-falls-main");
		if(settings.classes && settings.classes.main){
			$(main).addClass(settings.classes.main);
		}
		
		//創建表格容器並賦予樣式
		var table = document.createElement("span");
		$(table).addClass("jquery-falls-table");
		if(settings.classes && settings.classes.table){
			$(table).addClass(settings.classes.table);
		}

		//創建底部容器並賦予樣式
		var end = document.createElement("div");
		$(end).addClass("jquery-falls-end");
		if(settings.classes && settings.classes.end){
			$(end).addClass(settings.classes.end);
		}
		
		//設置底部內容
		$(end).html(settings.endText || "End");
		//將表格容器添加到主容器中
		$(main).append($(table));
		//將主容器添加到頂級容器中
		$(container).append($(main));
		//將底部容器添加到頂級容器中
		$(container).append($(end));
		//生成列容器並賦予樣式且添加到表格容器中
		generateColumns();
		//將整個瀑布佈局添加到外部容器中,即渲染到頁面
		settings.parent.append($(container));
		
		//監聽滾動事件,當底部容器進入到視野時,觸發事件
		$(settings.container || window).scroll(checkToNext);
		//爲底部綁定點擊翻頁事件,避免頁面加載出現異常無法翻下一頁的問題
		$(".jquery-falls-end").click(function(){
			settings.next(me,page);
		});
		
		/**
		 * 檢查尾部爲知並判斷是否觸發下一頁事件
		 */
		function checkToNext(){
			if(!$.belowthefold($(".jquery-falls-end"), {
				container: settings.container || window,
				threshold: settings.threshold - $(end).height()
			})){
				settings.next(me,page);
			}
		}
		
		/**
		 * 生成列容器並賦予樣式且添加到表格容器中
		 */
		function generateColumns(){
			for(var i = 0; i < settings.column; i++){
				var column = document.createElement("div");
				$(column).addClass("jquery-falls-column");
				if(settings.columnWidth){
					$(column).width(settings.columnWidth);
				}
				$(table).append($(column));
			}
		}
		
		/**
		 * 獲取所有列容器
		 * @returns columns
		 */
		function getColumns(){
			return $(".jquery-falls-column");
		}
		
		/**
		 * 清除所有列內容
		 */
		function clearColumns(){
			getColumns().remove();
			return me;
		}
		
		/**
		 * 加載一頁內容
		 * @param imgs 加載內容
		 * @returns me 瀑布佈局對象,便於連續動作
		 */
		this.load = function(imgs){
			//獲取列容器數組
			var cols = getColumns();
			(function showImg(){
				//如果內容已經取空表示當前頁面加載完畢,判斷是否需要繼續加載下一頁
				if(imgs.length == 0){
					checkToNext();
					return;
				}
				//獲取一項內容
				var img = imgs.pop();
				//內容無效跳過該內容渲染,執行下一步
				if(isEmpty(img)){
					window.setTimeout(showImg,settings.delay);
					return;
				}
				//獲取插入列索引
				var index = getIndex(cols);
				//生成臨時id,便於處理內容對象dom
				var id = "_id_" + new Date().getTime();
				var dom =  content.replace("${id}", id);
				//向列容器添加內容
				cols.eq(index).append(dom);
				//賦予內容中個元素樣式
				if(settings.classes && settings.classes.content){
					$("#" + id).addClass(settings.classes.content);
				}
				if(settings.classes && settings.classes.link){
					$("#" + id).find("a").addClass(settings.classes.link);
				}
				if(settings.classes && settings.classes.image){
					$("#" + id).find("img").addClass(settings.classes.image);
				}
				if(settings.classes && settings.classes.text){
					$("#" + id).find("p").addClass(settings.classes.text);
				}
				//設置跳轉鏈接
				$("#" + id).find("a").attr("href", img.href || "javascript:void(0);").attr("title", img.title || "");
				//設置圖片並設定加載失敗時顯示空圖
				$("#" + id).find("img").error(function(){
					$(this).attr("src", settings.empty);
				}).attr("src", img.src)
				//設定圖片寬度,和列容器寬度一致
				.width(cols.eq(index).width());
				//填充描述內容
				$("#" + id).find("p").html(img.text || "");
				//漸現渲染內容並移除臨時id
				$("#" + id).fadeIn(settings.fadeIn).removeAttr("id");
				//延遲加載下一內容
				window.setTimeout(showImg,settings.delay);
			})();
			//每一次加載表示翻了一頁
			page++;
			return me;
		}
		
		/**
		 * 重新設置列數
		 * 用於實現窗口大小變更時重新計算列數並重新排布內容
		 */
		this.resetColumn = function(column){
			if(settings.column == column){
				return;
			}
			//設置調整列數
			settings.column = column;
			settings.column = settings.column < 1 ? 1 : settings.column;
			var contents = [];
			var heights = [];
			//生成預備內容數組和高度數組
			for(var i = 0; i < settings.column; i++){
				contents[i] = "";
				heights[i] = 0;
			}
			/**
			 * 獲取高度最低的列
			 */
			function getInsertContentIndex(){
				//緩存最低高度列
				var minHeight = new Array();
				for(var i = 0; i < heights.length; i++){
					if(minHeight.length == 0){
						minHeight.push({index :i, height : heights[i]});
					}else{
						//如果有更低的列,清空緩存的最低高度的列容器數組,放入最新的列容器
						if(minHeight[0].height > heights[i]){
							minHeight = new Array();
							minHeight.push({index :i, height : heights[i]});
						//如果有一樣低的列容器,放入數組
						}else if(minHeight[0].height == heights[i]){
							minHeight.push({index :i, height : heights[i]});
						}
					}
				}
				//如果只有一個最低列,直接選擇該列,否則從中隨機取一列
				if(minHeight.length > 1){
					var random = getRandom(minHeight.length - 1);
					return minHeight[random].index;
				}else if(minHeight.length == 0){
					return 0;
				}else{
					return minHeight[0].index;
				}
			}
			//獲取現有內容
			var contentDoms = getColumns().find(".jquery-falls-content");
			//重新計算內容排布
			for(var i = 0; i < contentDoms.length; i++){
				var index = getInsertContentIndex();
				heights[index] += contentDoms.eq(i).height();
				contents[index] += contentDoms.eq(i)[0].outerHTML;
			}
			//清除列內容
			clearColumns();
			//重新渲染列
			generateColumns();
			//向列中添加內容
			for(var i = 0;i < contents.length; i++){
				getColumns().eq(i).append(contents[i]);
			}
			//渲染後檢查是否可以觸發下一頁
			checkToNext();
			return me;
		}
		
		return this;
    }
})(jQuery, window, document);


 

CSS代碼:

/*瀑布佈局頂級容器,主要設置背景色,設置背景色的目的是與column區別開來*/
.jquery-falls-container {
	background-color: #efefef;
}
/*主容器,用於做居中佈局*/
.jquery-falls-main {
	text-align: center;
}
/*表格容器,用於放置列,必須爲inline-block,其他樣式可調整,背景色建議與頂級容器保持一致或者不設置*/
.jquery-falls-table {
	display: inline-block;
}
/*列容器,必須爲inline-block和float left,其他樣式可調整,由於寬度可以通過創建瀑布佈局時指定,因此不建議修改該類*/
.jquery-falls-column {
	float: left;
	display: inline-block;
	width: 200px;
	margin: 5px;
}
/*內容容器,必須爲inline-block,其他樣式可調整,但不建議調整*/
.jquery-falls-content {
	display: inline-block;
	margin-top: 5px;
	margin-bottom: 5px;
	background-color: white;
	vertical-align: top;
}
/*圖片樣式,不建議更改*/
.jquery-falls-content img {
	width: 100%;
	border: 0px;
}
/*鏈接樣式,不建議更改*/
.jquery-falls-content a {
	width: 100%;
	text-decoration: none;
	color: black;
}
/*文字描述容器,不建議更改,但對於兼容低版本IE時,min-height可能無效,需要設置height*/
.jquery-falls-about {
	vertical-align: top;
	width: 100%;
	min-height: 50px;
	font-size: 12px;
	background-color: white;
}
/*文字描述段落容器,不建議更改,但可以適當添加margin做縮進*/
.jquery-falls-about p {
	padding: 15px;
	margin: 0px;
	text-align: left;
}
/*瀑布佈局結尾容器,可隨意更改樣式*/
.jquery-falls-end {
	padding: 30px;
	vertical-align: middle;
	text-align: center;
	font-size: 40px;
	font-family: "微軟雅黑", fantasy, sans-serif, serif;
}

 

使用時引用如下:

<link rel="stylesheet" type="text/css" href="css/jquery.falls.css"/>
<script type="text/javascript" src="js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="js/jquery.falls.js"></script>


使用範例:

$("#container").falls({
	column: 4,
	columnWidth: 250,
	endText : 'The End',
	next : function(falls,page){
		//load page data into var "data"
		falls.load(data);
	}
}).load(data);


OVER!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章