引言
最近公司有个项目要用到木桶布局,找了很多插件都感觉不合适,于是决定自己撸一个。已经开源到github。(文章写的匆忙,有错别字或bug请指正)
- github github.com/Jon-Millent…
- 演示 show.verydog.cn/iboot
什么是木桶布局,我们随便百度一张图片就知道了。如下图。 那么问题来了,要怎么实现这种效果呢?本人想了一种思路,看下图。
首先排列若干图片,用一个基准高度来设置他们,让后当放不下的时候在进行整体缩放。
实现
让我们愉快的coding吧~,准备把它封装成一个jQ插件。考虑设计以下方法
- loader 图片加载器
- render 图片渲染器
- template 模板渲染器
- resize 重新排版器
- loadMore 动态加载图片
那么让我们创建iboot.js。
;(function ($, root) {
function Iboot() {
}
})(jQuery, window);
这样实现了一个简单的jq插件模板。由于某些参数需要传递过来,我们要继续完善。
function Iboot(ele, config) {
this.ele = $(ele)
this.eleWidth = this.ele.width()
this.config = $.extend({ // 这里会合并用户传来的参数
baseHeight: 400, // 默认基准高度
list: [ // 定义图片格式
{
src: '',
alt: 'xxx'
}
],
template: function (dom) {
return dom
},
scrollBox: $(document.body),
afterLoad: function () { //加载之前回调
},
beforeLoad: function () { //加载之后回调
}
}, config)
this.innerData = { //内置的储存器
nowItemWidth: 0, //现在的增加长度
appendDoms: [],
scrollBoxData: {
},
saveDom: [],
groupid: 0
}
}
实现图片加载器
首先我们要实现图片加载器,代码如下。
Iboot.prototype.loader = function(list, success, error, done){
var now = 0;
$.each(list, function (i, v) {
(function (i, v) {
var img = new Image()
img.onload = function () {
success(v, img)
now++
if(now === list.length) {
done()
}
}
img.onerror = function (err) {
error(v, img, err)
now++
if(now === list.length) {
done()
}
}
img.src = v.src
img.alt = v.alt
})(i, v);
})
}
加载传入的list,加载后分别指派给回调函数 success(成功之后)error(加载失败)done(全部加载完成)
实现模板函数
Iboot.prototype.template = function(src, alt){
var item = $('<div class="iboot-item" style="float: left"></div>');
item.append(
$('<img src="'+src+'" alt="'+alt+'" style="width: 100%;height: auto;">')
);
return this.config.template(
item
)
}
这和函数允许使用用户传来的回调函数在渲染之前操作一下模板,用来DIYdom,比如(加边距,加文字等等)
【核心】实现render渲染函数
Iboot.prototype.render = function(cp, img){
var _this = this
// 获取一下item模板
var dom = this.template(cp.src, cp.alt)
// 获取加载后的图片的比例
var scale = img.width / img.height
// 给item加属性,保存当前信息,用给Resize的时候读取,根据信息重新排版
dom.attr({
'data-rew': img.width,
'data-reh': img.height,
'data-scale': scale
})
// 计算根据基准高度缩放之后的信息
var comp = {
width: scale * this.config.baseHeight,
height: this.config.baseHeight,
}
// nowItemWidth的作用就是没次加元素,就把宽度累加,然后当累加的宽度大于父盒子的宽度的时候,进行整体缩放,缩放完成之后重置为0
this.innerData.nowItemWidth += comp.width
// appendDoms储存当前列表,缩放之后清空
this.innerData.appendDoms.push(dom)
// saveDom是全局的,所有信息都储存在这,用于之后resize
this.innerData.saveDom.push(dom)
// 缩放当前dom
dom.css({
height: comp.height,
width: comp.width
})
// 添加到父盒子
this.ele.append(dom)
var compW = this.innerData.nowItemWidth - this.ele.width()
// 当放不下的时候,进行整体缩放
if(compW > 0) {
// 超出大小整体缩放
var nowScale = this.innerData.nowItemWidth / this.getMediaHeight()
var scaleHeight = this.ele.width() / nowScale
// 整体缩放
$.each(this.innerData.appendDoms, function (i, v) {
var data = $(v).data()
$(v).css({
height: scaleHeight,
width: data.scale * scaleHeight
})
// 对缩放后的元素进行分组,这个分组很重要,因为当图片小于排版要求的时候就不会分组,不分组的就隐藏掉。
$(v).attr('group', _this.innerData.groupid)
})
this.innerData.nowItemWidth = 0
this.innerData.appendDoms = []
this.innerData.groupid++
}
}
总结一下render的思路
- 累计添加子元素,把等比例缩放后的宽度储存起来
- 当放不下的时候进行整体缩放
- 请空储存的值
- 以此类推
关于 groupid
,因为图片会出现不符合排版要求的情况,我们不对其进行分组,隐藏它们。当resize或者loadmore的时候符合分组要求在显示它们。
实现resize排版器
resize的思想和render很像,只不过就是考虑了groupid的显示隐藏。
Iboot.prototype.resize = function(){
var baseHeight = this.getMediaHeight()
var _this = this
$.each(this.innerData.saveDom, function (i, v) {
// 首先删除所有的group id
$.each(_this.innerData.appendDoms, function (i, v) {
$(v).removeAttr('groupid')
$(v).show()
})
var data = $(v).data()
var cof = {
height: baseHeight,
width: baseHeight * data.scale
}
_this.innerData.appendDoms.push(v)
_this.innerData.nowItemWidth += cof.width
var compW = _this.innerData.nowItemWidth - _this.ele.width()
if(compW > 0) {
// 超出大小整体缩放
var nowScale = _this.innerData.nowItemWidth / _this.getMediaHeight()
var scaleHeight = _this.ele.width() / nowScale
$.each(_this.innerData.appendDoms, function (i, v) {
var data = $(v).data()
$(v).css({
height: scaleHeight,
width: data.scale * scaleHeight
})
$(v).attr('group', _this.innerData.groupid)
})
_this.innerData.nowItemWidth = 0
_this.innerData.appendDoms = []
_this.innerData.groupid++
}
})
_this.innerData.nowItemWidth = 0
_this.innerData.appendDoms = []
_this.innerData.groupid = 0
// 隐藏没有group id 的
$.each(this.innerData.saveDom, function (k, v) {
var data = $(v).attr('group')
if(!data) {
$(v).hide()
}
})
}
loadMore加载更多
Iboot.prototype.loadMore = function(list){
var _this = this
// 把上次隐藏groupid的加进去
var coc = []
$.each(this.innerData.saveDom, function (i, v) {
var data = $(v).attr('group')
if(!data) {
if(!v) {
return
}
var img = $(v).find('img')
coc.push({
src: img.attr('src'),
alt: img.attr('alt'),
})
_this.innerData.saveDom.splice(i, 1)
}
})
list = coc.concat(list)
this.beforeLoad()
this.loader(list, function (cp, img) {
_this.render(cp, img)
}, function (cp, img, error) {
}, function () {
_this.afterLoad()
})
}
最后把方法抛出到全局
root.Iboot = function(ele, config){
return new Iboot(ele, config).init();
}
大功告成,前前后后大概用了4个多小时,头发要掉光了。。。学无止境,告辞!