其实瀑布布局已经流行很久了,以前一直很喜欢,就是没实现过。近日抽空研究了一下,做出了下面的实现。
上代码前,先大概说一下原理:
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!