- 做爲一個寫靜態的切圖仔,其實平常工做中根本用不上瀑布流這種小清新,畢竟營銷頁面都是要求 搶眼、吸睛、 高大上 (文案爸爸說啥都對)。
- 昨上午閒着沒事看到別人寫的瀑布流的帖子,以爲很好玩的樣子,而後決定上午就寫一個試試。。。因此,今天下午,就來整理下這過程當中的一些思路。
寫代碼以前大概列了一下需求,而後中間又加上了一些其餘功能,最終的需求以下:html
起一個自執行函數,只須要暴露一個 falls 變量,該變量指向一個包含 init 方法的對象; init 方法有2個參數: - el:瀑布流的容器的選擇符 - options:其餘參數 ps: 這裏還用到了一個自定義的 extend 方法,用於合併默認屬性以及自定義屬性對象,怕被說兼容性很差就沒有用 ES6 語法,參照 Object.assign 方法,完整代碼裏也有,此處很少介紹;github
var falls = (function () {
var defaults = {
};
var Falls = function (el, options) {
};
var prototype = Falls.prototype;
var init = function (el, options) {
options = extend([], defaults, options);
return new Falls(el, options).init();
};
return {
init: init
}
})();
複製代碼
接下來肯定一些能夠定義的屬性: - width: 圖片(圖片外容器)的寬度 - colSpace: 列間距 - rowSpace: 行間距 - itemClass: 圖片外容器類名,方便修改邊框、陰影等樣式 根據這些屬性,隨手肯定了各項默認值數組
var defaults = {
width: 220,
colSpace: 10,
rowSpace: 10,
itemClass: '_list-item'
};
複製代碼
定義 Falls 構造函數,最終該構造函數有如下初始屬性(屬性在後面用到再作解釋):bash
var Falls = function (el, options) {
this.el = document.querySelector(el);
this.imgList = options.imgList;
this.colSpace = options.colSpace;
this.rowSpace = options.rowSpace;
this.width = options.width;
this.itemClass = options.itemClass;
this.first = true;
this.startIndex = 0;
this.callback = [];
this.loadAll = false;
};
複製代碼
在添加原型方法以前:app
var prototype = Falls.prototype;
複製代碼
這樣能夠少寫好多字母呢,真棒!!!框架
prototype.initialize = function () {
var rootEl = document.createElement('div');
rootEl.style.margin = '0 auto';
rootEl.style.position = 'relative';
this.rootEl = rootEl;
this.el.appendChild(this.rootEl);
};
複製代碼
這裏定義了根元素 rootEl ,並給它添加了相對定位及 margin 屬性,這樣整個根元素會在容器中水平居中。dom
這裏有一些前置屬性及初始邏輯函數
prototype.loadFalls = function () {
var wrapWidth = this.el.clientWidth; // 獲取容器的寬度
this.colWidth = this.width + this.colSpace; // 單個圖片加上列間隙須要的寬度
this.col = Math.floor((wrapWidth + this.colSpace) / this.colWidth); // 獲取圖片列數
this.rootEl.style.width = this.col * this.colWidth - this.colSpace + 'px'; // 根元素的寬度
if (this.first) { // 若是初次渲染,直接執行
this.storageTop();
this.addItem();
this.first = false;
this.lastCol = this.col;
} else { // 非初次渲染,判斷列數是否變化
if (this.lastCol !== this.col) {
this.startIndex = 0; // 列數變化時,所有從新渲染
this.rootEl.innerHTML = ''; // 清空根元素
this.storageTop();
this.addItem();
this.lastCol = this.col;
}
}
};
複製代碼
以代碼空行來拆分:佈局
第一部分:屬性定義,見註釋。
第二部分:邏輯部分
prototype.storageTop = function () {
var topArr = [];
for (var i = 0; i < this.col; i++) {
topArr.push({
left: this.colWidth * i,
top: 0
})
}
this.topArr = topArr;
};
複製代碼
根據列數生成一個存儲每列下一張圖片 top 及 left 值的數組,top 初始都爲 0 ,left 爲每列的寬度 * 列數;
prototype.addItem = function () {
var _this = this,
maxHeight = 0,
topArr = this.topArr,
imgList = this.imgList.slice(_this.startIndex),
len = topArr.length;
(function addImg() {
var current = imgList.shift(),
top = topArr[0].top,
index = 0;
for (var j = 1; j < len; j++) { // 遍歷求出當前最小 top 值,及對應的列數 index
if (topArr[j].top < top) {
top = topArr[j].top;
index = j;
}
}
var item = document.createElement('div'); // 建立圖片包裹元素
item.style.position = 'absolute';
item.style.top = top + 'px';
item.style.left = topArr[index].left + 'px';
item.style.width = _this.width + 'px';
item.style.boxSizing = 'border-box';
item.classList.add(_this.itemClass);
var img = document.createElement('img'); // 建立圖片元素
img.style.width = '100%';
img.src = current.src;
img.alt = current.alt;
item.appendChild(img);
_this.rootEl.appendChild(item);
img.onload = function () {
topArr[index].top += item.offsetHeight + _this.rowSpace; // 新增圖片後更新高度數組
maxHeight = maxHeight < topArr[index].top ? topArr[index].top : maxHeight;
_this.rootEl.style.height = maxHeight + 'px'; // 更新容器的高度
if (imgList.length) {
addImg();
} else {
_this.startIndex = _this.imgList.length;
if (!_this.callback.length) {
_this.loadAll = true;
} else {
_this.callback.shift()();
}
}
};
})();
};
複製代碼
這一塊有點長,由於有兩處爲 dom 對象添加屬性,主要邏輯以下:
只有在圖片加載完成後才能獲取圖片高度,進行 topArr 的更新
- 記錄圖片索引至 startIndex ,新增圖片數據後只需從 startIndex 位置開始添加
- 檢查回調隊列 callback 內是否有回調事件,若是有,取出第一條進行處理(回調隊列後面解釋)
prototype.bindEvent = function () { // 經過 resize 事件監聽容器寬度變化
window.addEventListener('resize', this.loadFalls.bind(this));
};
複製代碼
爲 window 對象的 resize 事件添加監聽,觸發 loadFalls 函數,這裏經過bind修正了方法內部的 this 指向;
prototype.init = function () { // 生成一個瀑布流實例
this.initialize();
this.loadFalls();
this.bindEvent();
return this; // 返回實例對象
};
複製代碼
就是依次調用 初始化 添加圖片 綁定事件 三個方法,這裏返回了 this ,用於保存當前實例,下一步會用到;
prototype.addImgReload = function (arr) {
var _this = this;
if (this.loadAll) {
this.imgList = arr;
this.addItem();
} else {
this.callback.push(function () {
_this.imgList = arr;
_this.addItem();
})
}
};
複製代碼
上面爲了這一步作了不少鋪墊
- this.loadAll 保存當前圖片隊列是否所有加載完成
- 若是當前圖片隊列已經加載完成,那就跟新圖片隊列 this.imgList ,繼續加載 this.addItem(),由於已經存儲了 startIndex ,因此會重新增的圖片繼續加載
- 若是當前圖片隊列還沒加載完成,將更新圖片的任務推動回調隊列 callback ,當前圖片隊列加載完成後會檢測回調隊列,取出更新圖片任務完成,就算有多個圖片更新事件也沒關係, callback 保持先進先出執行順序;
經過 addImgReload 方法分次跟新圖片屬性能夠實現懶加載
圖片數據能夠根據實際擴充,此處只添加了 src 及 alt ,包括超連接能夠修改外層容器 item 或者再套一層;
代碼寫完沒有作優化,有幾段比較長,有空再優化吧,畢竟快下班了
最後,我的能力有限,歡迎大佬補充,謝謝!!!
編輯文章的時候發現了一個坑,圖片加載失敗會阻塞後續圖片,明天改