- 做爲一個寫靜態的切圖仔,其實平常工做中根本用不上瀑布流這種小清新,畢竟營銷頁面都是要求 搶眼__、__吸睛__、 __高大上 (文案爸爸說啥都對)。
- 昨上午閒着沒事看到別人寫的瀑布流的帖子,以爲很好玩的樣子,而後決定上午就寫一個試試。。。因此,今天下午,就來整理下這過程當中的一些思路。
寫代碼以前大概列了一下需求,而後中間又加上了一些其餘功能,最終的需求以下: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 構造函數,最終該構造函數有如下初始屬性(屬性在後面用到再作解釋):
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; };
在添加原型方法以前:
var prototype = Falls.prototype;
這樣能夠少寫好多字母呢,真棒!!!app
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 屬性,這樣整個根元素會在容器中水平居中。框架
這裏有一些前置屬性及初始邏輯
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; } } };
以代碼空行來拆分:dom
第二部分:__邏輯部分__函數
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 方法分次跟新圖片屬性能夠實現懶加載