瀑布流效果類封裝
前言:瀑布流,隨着瀏覽器滾動,頁面小單元逐漸出現,且位置是不規則的出現,常用在商城網站,圖片類網站(花瓣網)等,十分美觀。
1.瀑布流思路
1.1 確定頁面可以分多少欄目
根據主體區域的寬度/第一個小塊的寬度,向下取整獲取可以分多少欄目
let { items, gap,dom } = this; //gap 間距 動態傳入
let oBoxWidth = dom.offsetWidth; //最外層box寬度
let itemWidth = items[0].offsetWidth; //子項
let colums = Math.floor(oBoxWidth / (itemWidth + gap)); //獲取行數目
1.2 加載的時候,如何把圖片插入到上一行高度最低的那一行
瀑布流最大的思路是如何確定好小塊的排序位置,一個數據列表如何按照規則往下排。
使用 Math.min.apply(null,arr) 確定最小索引。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳
let { items, gap,dom } = this;
let oBoxWidth = dom.offsetWidth;
let itemWidth = items[0].offsetWidth;
let colums = Math.floor(oBoxWidth / (itemWidth + gap)); //獲取行數目
let arr = [], //新建數組和當前最小下標
minIndex;
for (let i = 0; i < items.length; i++) {
//第一行正常排序
if (i < colums) {
items[i].style.top = 0;
items[i].style.left = i * (itemWidth + gap) + "px";
arr.push(items[i].offsetHeight);
} else {
//第一行以後
minIndex = this.getMinIndex(arr); //注意
items[i].style.left = items[minIndex].offsetLeft + "px";
items[i].style.top = arr[minIndex] + gap + "px";
arr[minIndex] = arr[minIndex] + items[i].offsetHeight + gap;
}
}
getMinIndex() {
let minHeight = Math.min.apply(null, arr);
return arr.findIndex(function (item) {
return item === minHeight;
});
}
1.3 頁面滾動到底部加載下一頁數據,執行回調函數
封裝的該類,接收一個回調函數,當確定要滾動下一頁數據時觸發。
條件爲: (頁面高度+頁面滾動條高度>=最後一元素的offsetTop)
class waterFull {
constructor({ dom = null, gap = 10, canScrollFn = () => { } }) {
this.dom = dom;
this.gap = gap;
this.items = this.dom.children;
}
scrollFn() {
let { items } = this;
let canscroll =
this.getScroll() + this.getClient().height >=
items[items.length - 1].offsetTop;
if (canscroll) {
this.canScrollFn()
}
}
getClient() {
return {
width:
document.body.clientWidth ||
document.documentElement.clientWidth ||
window.innerWidth,
height:
document.body.clientHeight ||
document.documentElement.clientHeight ||
window.innerHeight
};
}
getScroll() {
return window.pageYOffset || document.documentElement.scrollTop;
}
}
2.問題,頁面初始化,拿不到圖片的高度問題。
經過以上分析和調整基本可以實現一個瀑布流類。
class WaterFull {
constructor({ dom = null, gap = 10, canScrollFn = () => { } }) {
this.dom = dom;
this.gap = gap;
this.items = this.dom.children;
this.canScrollFn = canScrollFn;
this.init()
this.wScroll();
this.wResize();
}
init() {
this.waterInit();
}
wResize() {
let that = this;
window.onresize = function () {
that.init()
};
}
wScroll() {
let that = this;
window.onscroll = throttle(function(){
that.scrollFn()
},100)
}
waterInit() {
let { items, gap,dom } = this;
let oBoxWidth = dom.offsetWidth;
let itemWidth = items[0].offsetWidth;
let colums = Math.floor(oBoxWidth / (itemWidth + gap)); //獲取行數目
let arr = [],
minIndex;
for (let i = 0; i < items.length; i++) {
//第一行
if (i < colums) {
items[i].style.top = 0;
items[i].style.left = i * (itemWidth + gap) + "px";
arr.push(items[i].offsetHeight);
} else {
//第一行以後
minIndex = this.getMinIndex(arr);
items[i].style.left = items[minIndex].offsetLeft + "px";
items[i].style.top = arr[minIndex] + gap + "px";
arr[minIndex] = arr[minIndex] + items[i].offsetHeight + gap;
}
}
}
scrollFn() {
let { items } = this;
let canscroll =
this.getScroll() + this.getClient().height >=
items[items.length - 1].offsetTop;
if (canscroll) {
this.canScrollFn()
}
}
getMinIndex(arr) {
let minHeight = Math.min.apply(null, arr);
return arr.findIndex(function (item) {
return item === minHeight;
});
}
getClient() {
return {
width:
document.body.clientWidth ||
document.documentElement.clientWidth ||
window.innerWidth,
height:
document.body.clientHeight ||
document.documentElement.clientHeight ||
window.innerHeight
};
}
getScroll() {
return window.pageYOffset || document.documentElement.scrollTop;
}
}
但隨之而來的是,當我們在vue裏面初始話時候,函數不執行(圖片堆疊在一起,沒有正確的分欄),經調試發現每一個box有寬度沒有高度,所以沒每一個box無法正確的定位。
2.1vue在mounted時候,無法獲取圖片的正常高度。
渲染時是同步渲染的,所以只有圖片onload時候纔會有高度,但是一個頁面有很多圖片不可能每一個onload都觸發init函數,這個時候可以利用一個閉包來合併執行,不需要多次觸發,收集所有的觸發條件,歸爲依次觸發即可
let proxySync = (function() {
let cache = [], // 保存一段時間內需要同步的 ID
timer; // 定時器
return function(id) {
cache.push(id);
if (timer) {
// 保證不會覆蓋已經啓動的定時器
return;
}
timer = setTimeout(function() {
callBack.apply(this,arguments) //回調擺正this
clearTimeout(timer); // 清空定時器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 500);
};
})();
3.優化加載問題
爲了避免頁面滾動觸發太過於頻繁,可以使用節流函數優化scrool,關於節流的原理和實現,大家可以參考
節流
最終得到的結果
var throttle = function (callBack, time=500) {
var prev = Date.now(),
first = true;
return function () {
if (first) {
callBack.apply(this, arguments);
return first = false;
}
var now = Date.now();
if (now - prev >= time) {
callBack.apply(this, arguments);
prev = Date.now();
}
}
}
/**
* dom 最外層的box gap 間距 canScrollFn 滑到最底部的回調
*/
class WaterFull {
constructor({ dom = null, gap = 10, canScrollFn = () => { } }) {
this.dom = dom;
this.gap = gap;
this.items = this.dom.children;
this.canScrollFn = canScrollFn;
this.init()
this.wScroll();
this.wResize();
}
init() {
this.waterInit();
}
wResize() {
let that = this;
window.onresize = function () {
that.init()
};
}
wScroll() {
let that = this;
window.onscroll = throttle(function(){
that.scrollFn()
},100)
}
waterInit() {
let { items, gap,dom } = this;
let oBoxWidth = dom.offsetWidth;
let itemWidth = items[0].offsetWidth;
let colums = Math.floor(oBoxWidth / (itemWidth + gap)); //獲取行數目
let arr = [],
minIndex;
for (let i = 0; i < items.length; i++) {
//第一行
if (i < colums) {
items[i].style.top = 0;
items[i].style.left = i * (itemWidth + gap) + "px";
arr.push(items[i].offsetHeight);
} else {
//第一行以後
minIndex = this.getMinIndex(arr);
items[i].style.left = items[minIndex].offsetLeft + "px";
items[i].style.top = arr[minIndex] + gap + "px";
arr[minIndex] = arr[minIndex] + items[i].offsetHeight + gap;
}
}
}
scrollFn() {
let { items } = this;
let canscroll =
this.getScroll() + this.getClient().height >=
items[items.length - 1].offsetTop;
if (canscroll) {
this.canScrollFn()
}
}
getMinIndex(arr) {
let minHeight = Math.min.apply(null, arr);
return arr.findIndex(function (item) {
return item === minHeight;
});
}
getClient() {
return {
width:
document.body.clientWidth ||
document.documentElement.clientWidth ||
window.innerWidth,
height:
document.body.clientHeight ||
document.documentElement.clientHeight ||
window.innerHeight
};
}
getScroll() {
return window.pageYOffset || document.documentElement.scrollTop;
}
}
export default WaterFull;