引言
最近公司有個專案要用到木桶佈局,找了很多外掛都感覺不合適,於是決定自己擼一個。已經開源到github。(文章寫的匆忙,有錯別字或bug請指正)
什麼是木桶佈局,我們隨便百度一張圖片就知道了。如下圖。
那麼問題來了,要怎麼實現這種效果呢?本人想了一種思路,看下圖。首先排列若干圖片,用一個基準高度來設定他們,讓後當放不下的時候在進行整體縮放。
實現
讓我們愉快的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個多小時,頭髮要掉光了。。。學無止境,告辭!