您的位置:首页 > Web前端 > JQuery

基于jQuery的高可定制的瀑布布局实现V2.0

2014-12-04 19:46 211 查看
1.0实现请参考之前的文章:/article/1780335.html

2.0的实现原理和1.0的差不多,但是与1.0不同的是,不管理具体内容的实现,只关注布局。因此,使用2.0会比1.0要麻烦,但是会更加灵活。

另外,2.0无需再依赖css样式文件,只要引入一个js,所有都搞定。而且支持自定义样式名称以便做高级定制。由于布局的关键样式都是通过css添加到document的,因此只要自定义的样式不使用!important关键字,就不会破坏布局。

具体的改变与支持情况,想见代码内注释说明。

先看效果图(该实例实现了视频类型资源的混入展示,实现了鼠标停留图片显示操作按钮“喜欢”和“收藏”):



使用代码(省略引入,省略自定义样式),这部分就没好好完善了,辛苦点看吧:

<span style="white-space:pre"><script type="text/javascript"></span>
<span style="white-space:pre">	</span>var width = 230;
	var page = 0;
	var headImage = "http://10.20.53.13/images/files/pdf1.png";
	$(document).ready(function(){
		var falls = $("#container").falls({
			//count: 5,
			width: width,
			trim: 50,
			classes: {
				content : "content-container"
			},
			end: "<span style=\"width: 100%;margin: 10px;border: 1px solid #efefef; background-color: white;text-align: center;padding: 15px;font-size: 20px;\">加载更多</span>"
		},loadNext);
		loadNext(falls);
	});
	

	function delaySetHeight(img, imgContainer){
		var imgHeight = img.height();
		if(imgHeight > 0){
			imgContainer.height(imgHeight);
			img.height(imgHeight);
		}else{
			window.setTimeout(function(){
				delaySetHeight(img, imgContainer);
			}, 100);
		}
	}
	
	function loadContent(row){
		var div = $(document.createElement("div"));
		div.addClass("content-div");
		var imgContainer = $(document.createElement("div"));
		imgContainer.addClass("content-img-div");
		var img = $(document.createElement("img"));
		img.error(function(){
			img.attr("src", falls.empty());
		});
		img.addClass("content-img");
		img.load(function(){
			delaySetHeight(img, imgContainer);
			if(row.type === "video"){
				var videoIcon = $(document.createElement("img"));
				videoIcon.attr("src", "images/icons/media_video.png");
				videoIcon.addClass("video-icon");
				imgContainer.append(videoIcon);
			}
		});
		img.attr("src", row.src);
		img.click(function(){
			window.open(row.href);
		});
		var toolbar = $(document.createElement("div"));
		toolbar.addClass("content-toolbar");
		toolbar.html("<div class=\"content-toolbar-left\">喜欢</div><div class=\"content-toolbar-right\">收藏</div>");
		imgContainer.mouseover(function(){
			imgContainer.find(".content-toolbar").show();
		});
		imgContainer.mouseout(function(){
			imgContainer.find(".content-toolbar").hide();
		});
		imgContainer.append(img);
		imgContainer.append(toolbar);
		toolbar.find(".content-toolbar-left").click(function(){
			alert(row.id);
		});
		toolbar.find(".content-toolbar-right").click(function(){
			alert(row.src);
		});
		div.append(imgContainer);
		var span = $(document.createElement("span"));
		span.addClass("content-about");
		span.append("<span class=\"content-about-text\">" + 
						"<img src=\"" + headImage + "\" style=\"width: 32px;height: 32px;padding: 2px; border: 1px solid #efefef;\"/>" + 
					"</span>" +
					"<span class=\"content-about-text\">" + row.text + "</span>")
		
		div.append(span);
		return div;
	}
	
	function loadNext(falls){
		$.ajax({
			url: "pins.action",
			dataType: 'html',
			type: "POST",
			async:false,
			data: {
				start: page * 20,
				limit: 20
			},
			success: function(html){
				var data = eval(html);
				if(data.length == 0){
					falls.setFinish(function(end){
						end.children().html("没有更多数据了");
					});
					return;
				}
				falls.load(data, loadContent);
			}
		});
		page++;
	}
</script>


JS代码如下:

/**
 * 瀑布布局
 * @author tomtrije
 * @version 2.0.0
 * @update 2014/12/04 V2.0.0
 * @since 2014/11/20 V1.0.0 旧版本更新描述省略
 * @update_list
 * 		V2.0.0
 * 		重构实现
			只负责布局实现,不再处理具体内容
			不再依赖CSS文件,关键布局样式动态赋予,并最小化样式设置
 * 		支持自适应列数,随窗口大小变化而变化
 * 		支持两边留空
 * 		支持定义间隙大小
 * 		支持自动填充,但自动填充最低高度限制下限100
 * 		支持指定内容渲染动画或实现自定义动画
 * 		支持渲染内容后回调处理
 * 		支持自定义布局样式类,实现自定义细节处理,但实现布局关键的css不会被调整
 * 		支持自定义滚动事件监听,但一般以window为监听对象,不建议更改
 * 			指定监听时,必须指定监听对象高度(固定高度),作计算翻页参考高度
 * 		不做图片异常自动替换,但提供空图给外部引用处理异常
 * 		不解析具体数据,提供解析适配器,由调用程序自行实现解析
 * -------------------------------------------------------------------
 * @desc 使用示例
 * -------------------------------------------------------------------
		var page = 0;
		var pageSize = 20;
		var falls = $("#container").falls({
			width: width,	//must
			trim: 50,		//not must
			end: "加载更多" 	//not must
		},loadNext);
		
		loadNext(falls);
		
		function loadNext(falls){
			$.ajax({
				url: "pins.action",
				dataType: 'json',
				type: "POST",
				async:false,
				data: {
					start: page * pageSize,
					limit: pageSize
				},
				success: function(data){
					if(data.length == 0){
						falls.setFinish(function(end){
							//TODO: end object of the end document dom
							//		changes end text or content when finish here
							end.html("没有更多数据了");
						});
						return;
					}
					//TODO: load data and drow documnent here
					//		program will invoke loadContent function to get each content document
					falls.load(data, loadContent);
				}
			});
			page++;
		}
		
		function loadContent(row){
			//TODO: generate document by row data
			return dom;
		}
 * -------------------------------------------------------------------
 * @param $ jQuery
 * @param window window对象
 * @param document document对象
 * @param undefined 用于忽略后续参数
 */
(function($, window, document, undefined) {
	//----------定义常量----------begin
	//允许设置的最低填充高度的下限
	var _MIN_LENGTH = 100;
	//异常显示图,内部不使用,但可以被外部引用实现错误图替换
	var _EMPTY_IMG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC";
	//----------定义常量----------end
	//----------定义属性----------begin
    var options = {
    	count : "auto", //*列数,必须为正数,为auto时自动计算列数
    	width : 230, //*列宽,必须为正数,必须指定,不能自动计算
    	split : 5, //*列间隔,必须为正数,必须指定,不能自动计算;实际间隔为指定值*2,即上下左右各有间隔
    	trim : 0, //两边留白宽度,用于自动计算列数,必须为正数
    	threshold: 0, //提早加载像素
    	delay : 20, //延迟填充内容
    	end : "End", //底部容器内容
    	//加载内容特效
    	effect : {
    		mode : "fadeIn",
    		delay : 50
    	},
    	classes:{
    		container : "jquery-falls-container",
    		main : "jquery-falls-main",
    		table : "jquery-falls-table",
    		end: "jquery-falls-end",
    		column : "jquery-falls-column",
    		content : "jquery-falls-content"
    	},
    	afterrender : $.noop(),//执行完内容渲染后的事件处理
    	parent : undefined, //布局所属容器,不指定时为创建布局所在dom
    	event : window,//监听滚动事件的window对象,可以指定为其他对象
    	minHeight: undefined //最低填充高度(>=100),用于设置加载第一页未填满当前窗口时自动加载第二页,以此类推
    }
    var $window = $(window);//window对象
    var nextFn = $.noop();//下一页事件
    var self;//瀑布布局对象
    var doms = {};//缓存dom对象
    var count = options.count;//列数
    var finish = false;//是否结束
    var nexting = false;//是否正在加载下一页
    var loading = false;//是否正在渲染
	//----------定义属性----------end
    

	//----------定义构造函数----------start
	/**
	 * 定义瀑布布局对象
	 * @param options 配置
	 * @returns 当前布局对象
	 */
    $.fn.falls = function(opts, fn) {
    	options = $.extend(
    		{
	    		parent : $(this)//整个瀑布布局容器
	    	}, 
	    	options, 
	    	opts
    	);
    	nextFn = fn;
    	self = this;
    	_init();

    	//----------定义业务私有函数----------start
        function _init(){
        	_1_clearDoms();
        	_2_generateContainers();
        	_3_generateColumns();
        	_4_addListeners();
        }
        
        /**
         * 清除容器
         */
        function _1_clearDoms(){
        	//移除所属容器下其他一切dom,以排除干扰
        	options.parent.children().remove();
        }

        /**
         * 创建布局容器
         */
        function _2_generateContainers(){
        	//设置所属容器
        	doms.parent = options.parent;
        	//创建顶级容器
        	doms.container = $(document.createElement("div"));
        	//创建主容器
        	doms.main = $(document.createElement("div"));
    		//创建表格容器并赋予样式
        	doms.table = $(document.createElement("div"));
    		//创建底部监听容器并赋予样式
        	doms.end = $(document.createElement("div"));
        	doms.end.html(options.end);
        	
    		//设置容器继承关系
    		doms.main.append(doms.table);
    		doms.main.append(document.createElement("div"));
    		doms.main.append(doms.end);
    		doms.main.append(document.createElement("p"));
    		doms.container.append(doms.main);
    		doms.parent.append(doms.container);
    		
        	//赋予各容器样式
        	$(doms.container).addClass(options.classes.container);
        	//----main----
    		$(doms.main).addClass(options.classes.main);
    		$(doms.main).css({
    			display : "block",
    			textAlign : "center"
    		});
    		//----table----
    		$(doms.table).addClass(options.classes.table);
    		$(doms.table).css({
    			display : "inline-block"
    		});
    		//----end----
    		$(doms.end).addClass(options.classes.end);
    		$(doms.end).css({
    			display : "inline-block",
    			textAlign: "center",
    			margin: 20
    		});
    	}
        
        /**
         * 创建列布局
         */
        function _3_generateColumns(skip){
    		//计算实际列宽度
			var oneColumnWidth = options.width + options.split * 2;
			if(!skip){
				//计算列数
	    		if(options.count == 'auto'){
	    			var w = $window.width();
	    			count = parseInt((w - (options.trim * 2))  / oneColumnWidth);
	    			count = count < 1 ? 1 : count;
	    			self.resize = _o_resize;
	    		}else{
	    			count = options.count;
	    			self.resize = $.noop();
	    		}
			}
			$(doms.table).width(oneColumnWidth * count);
			$(doms.end).width(oneColumnWidth * count);
    		doms.columns = new Array();
    		//创建列
			for(var i = 0; i < count; i++){
	    		//创建列
				var column = $(document.createElement("div"));
				//使用相对定位,便于列内做相对于父容器的绝对定位
				column.css({
					float : "left",
					display : "inline-block",
					width: options.width,
					margin: options.split
				});
				//给列添加样式
				column.addClass(options.classes.column);
				doms.table.append(column);
				doms.columns.push(column);
			}
        }
        /**
         * 监听滚动事件
         */
        function _4_addListeners(){
        	$(options.event || window).scroll(_next);
        	$(options.event || window).resize(self.resize);
        }
        
        /**
         * 检验并触发下一页事件
         */
        function _next(){
        	if(loading){
        		return;
        	}
        	if(finish){
				return;
			}
			if(!belowthefold({
				element: doms.end, 
				container: options.event || window,
				threshold: options.threshold
			})){
				nexting = true;
				nextFn(self);
			//当前高度小于自动填充高度,即未填满容器,自动加载下一页内容
			}else if(options.minHeight && _getMinHeight() < options.minHeight){
				if(nexting){
					return;
				}
				nexting = true;
				nextFn(self);
			}else{
				nexting = false;//表示下一次滚动到底部可以触发下一页,同时表示当前已在底部
			}
        }
        
        /**
         * 销毁对象
         */
        function _destroy(){
        	$(self).remove();
        }
        
        /**
         * 当窗口大小变更时执行的变更
         */
        function _o_resize(){
        	//计算列数
        	var oneColumnWidth = options.width + options.split * 2;
			var w = $window.width();
    		self.resetColumn(parseInt((w - (options.trim * 2))  / oneColumnWidth));
        }
        

		/**
		 * 获取列最低高度
		 * @returns height 最低高度
		 */
		function _getMinHeight(){
			var minHeight = options.minHeight;
			for(var i = 0; i < doms.columns.length; i++){
				minHeight = minHeight > doms.columns[i].height() ? doms.columns[i].height() : minHeight;
			}
			return minHeight;
		}

		/**
		 * 获取列最大高度
		 * @returns height 最大高度
		 */
		function _getMaxHeight(){
			var maxHeight = 0;
			for(var i = 0; i < doms.columns.length; i++){
				maxHeight = maxHeight < doms.columns[i].height() ? doms.columns[i].height() : maxHeight;
			}
			return maxHeight;
		}
		
		/**
		 * 获取高度最低的列,即选择的插入列
		 */
		function _getMinColumn(){
			//记录每个列的高度和序号
			var lengthArray = new Array();
			for(var i = 0; i < doms.columns.length; i++){
				var obj = {};
				obj.len = doms.columns[i].height();
				obj.index = i;
				obj.dom = doms.columns[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].dom;
			}else if(minIndexs.length > 0){
				return minIndexs[0].dom;
			}
			return undefined;
		}
		
		/**
		 * 内容容器加载特效
		 * @param content
		 */
		function _effectContent(content){
			switch(options.effect.mode){
			case "fadeIn":
				content.fadeIn(options.effect.delay || 0, function(){
					_afterrender(content);
				});
				break;
			case "fadeTo":
				content.fadeTo(options.effect.delay || 0, options.effect.opacity || 1, function(){
					_afterrender(content);
				});
				break;
			case "show":
				content.show(options.effect.delay || 0, function(){
					_afterrender(content);
				});
				break;
			case "slideDown":
				content.slideDown(options.effect.delay || 0, function(){
					_afterrender(content);
				});
				break;
			case "slideUp":
				content.slideUp(options.effect.delay || 0, function(){
					_afterrender(content);
				});
				break;
			case "animate":
				content.animate(options.effect.properties, options.effect.options, function(){
					_afterrender(content);
				});
				break;
			}
		}
		
		/**
		 * 渲染回调
		 * @param content 内容dom对象
		 */
		function _afterrender(content){
			if($.isFunction(options.afterrender)){
				options.afterrender(content, content.prop("org-data"));
			}
		}
		
    	//----------定义业务私有函数----------end

    	//----------定义公共函数----------start
    	
        /**
         * 获取空图片
         * @returns 空图片
         */
    	this.empty = function(){
    		return _EMPTY_IMG;
    	}
    	
		/**
		 * 设置结束内容
		 * @param fn 处理结束
		 */
		this.setFinish = function(fn){
			finish = true;
			if(fn){
				fn(doms.end);
			}
		}
		
		/**
		 * 手工出发下一页事件
		 */
		this.next = function(){
    		nexting = false;
    		_next();
    	}
    	
    	/**
    	 * 加载一页内容
    	 * @param data 一页数据
    	 * @pram fn 用于建立内容dom的函数,由外部自动根据内容生成
    	 */
    	this.load = function(data, fn){
    		if(finish){
    			return;
    		}
    		if($.isEmptyObject(data)){
    			return;
    		}
			//数组反转 ,避免pop时倒序显示        
			var i = new Array();
			while(data.length > 0){
				i.push(data.pop());
			}
			data = i;
			doms.end.hide();
			loading = true;
    		(function appendContent(){
				//如果内容已经取空表示当前页面加载完毕,判断是否需要继续加载下一页
				if(data.length == 0){
		    		loading = false;
					doms.end.show();
					_next();
					//doms.table.height(_getMaxHeight());
					return;
				}
				var row = data.pop();
				//内容无效跳过该内容渲染,执行下一步
				if($.isEmptyObject(row)){
					window.setTimeout(appendContent,options.delay);
					return;
				}
				//获取插入内容
				var dom = fn(row);
				//获取插入列
				var column = _getMinColumn();
				//生成内容容器
				var content = $(document.createElement("div"));
				//缓存原始数据
				content.prop("org-data", row);
				//建立内容容器继承关系
				content.append(dom);
				//设置内容容器样式
				content.addClass(options.classes.content);
				content.css({
					display : "block",
					position: "relative",
					width: options.width,
					marginTop: options.split * 2,
					marginBottom: options.split * 2
				});
				content.hide();
				column.append(content);
				_effectContent(content);
				//延迟加载下一内容
				content.ready(function(){
					window.setTimeout(appendContent,options.delay);
				});
			})();
    		return self;
    	}
    	
    	/**
		 * 重新设置列数
		 * 用于实现窗口大小变更时重新计算列数并重新排布内容
		 * @param n 列数
		 */
		this.resetColumn = function(n){
			if(count == n){
				return;
			}
			//设置调整列数
			count = n;
			count = count < 1 ? 1 : count;
			var contents = [];
			var heights = [];
			//生成预备内容数组和高度数组
			for(var i = 0; i < count; 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 = $("." + options.classes.content);
			//重新计算内容排布
			for(var i = 0; i < contentDoms.length; i++){
				var index = getInsertContentIndex();
				var content = contentDoms.eq(i)[0].outerHTML;
				heights[index] += contentDoms.eq(i).height();
				contents[index] += content;
			}
			//清除列内容
			$("." + options.classes.columns).remove();
			//重新渲染列
			_3_generateColumns(true);
			//向列中添加内容
			for(var i = 0;i < contents.length; i++){
				var col = doms.columns[i];
				col.append(contents[i]);
			}
			//渲染后检查是否可以触发下一页
			_next();
			return self;
		}
    	
    	//----------定义公共函数----------end
    	
    	return this;
    }
	//----------定义构造函数----------end

	//----------定义工具私有函数----------start
	/**
	 * 获取随机数
	 * @param n 最大数
	 * @returns 随机数
	 */
	function getRandom(n){
		return Math.floor(Math.random()*n+1)
	}
	
	/**
	 * 判断内容是否还在容器视野底下
	 * 进入容器时触发加载事件
	 * @param settings 参数
	 * @returns boolean true 还在容器视野底下 false 已进入容器视野
	 */
	function belowthefold(sets) {
		var fold;
		if (sets.container === undefined || sets.container === window) {
		    fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop();
		} else {
		    fold = $(sets.container).offset().top + $(sets.container).height();
		}
		//如果内容顶部减去预载高度大于容器底部,说明还在可以不用加载
		return fold <= $(sets.element).offset().top - sets.threshold;
	}
	//----------定义工具私有函数----------end
    
})(jQuery, window, document);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: