1.0实现请参考之前的文章:http://blog.csdn.net/u014184244/article/details/41314477
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);