前言
瀑布流布局在我們現(xiàn)在的前端頁面中經(jīng)常會(huì)用的到聚请,它可以有效的降低頁面的復(fù)雜度怒竿,節(jié)省很多的空間砍鸠,對(duì)于整個(gè)頁面不需要太多的操作,只需要下拉就可以瀏覽用戶需要看到的數(shù)據(jù)耕驰;并且爷辱,在當(dāng)前這個(gè)APP至上的時(shí)代,瀑布流可以提供很好的用戶體驗(yàn)朦肘,通過結(jié)合下拉刷新饭弓,上拉加載進(jìn)行數(shù)據(jù)的懶加載等操作,對(duì)于用戶的體驗(yàn)感來說是接近于滿分的媒抠!
本文參考鏈接:淺談瀑布流 - 作者:evenyao弟断,感謝作者提供的思路以及圖片。
正文
瀑布流的特點(diǎn)
其實(shí)瀑布流的特點(diǎn)就是參差不齊的排列方式领舰,以及流式布局的擴(kuò)展性夫嗓,可以通過界面展示給用戶多條數(shù)據(jù),并且讓用戶可以有向下瀏覽的沖動(dòng)冲秽。
瀑布流的原理
通過將元素定位舍咖,并且通過javascript動(dòng)態(tài)的計(jì)算每一項(xiàng)元素的所處位置,給元素動(dòng)態(tài)的設(shè)置left和top值锉桑,讓它待在自己應(yīng)該在的位置排霉。
即多行等寬元素排列,后面的元素依次添加到其后民轴,等寬不等高攻柠,依次按照規(guī)則放入指定位置球订。
瀑布流的位置分析:淺談瀑布流 - 作者:evenyao
當(dāng)?shù)谝慌排艥M足夠多的等寬圖片時(shí)(如下圖情況),自然而然的考慮到之后放置的圖片會(huì)往下面排放瑰钮。
那么第六張圖片冒滩,放置在什么位置呢?按我們正常的邏輯來看浪谴,應(yīng)該是下圖的位置么开睡?
但是,在瀑布流中苟耻,我們的第六張圖的位置應(yīng)該是第一行中的高度最低的那一張圖片的下方篇恒。
為什么呢?
因?yàn)榉胖盟靶渍龋@一列的高度為所有列中最小胁艰,所以會(huì)放置在這個(gè)地方。
所以我們知道了智蝠,如果再繼續(xù)放置下去腾么,第七張圖片應(yīng)該是這個(gè)位置,對(duì)嗎寻咒?
通過瀑布流算法實(shí)驗(yàn)得出位置正確哮翘。看懂這個(gè)圖示應(yīng)該就能理解了瀑布流的原理算法毛秘。
代碼實(shí)現(xiàn)
對(duì)于我自己實(shí)現(xiàn)的這個(gè)瀑布流demo來說饭寺,相較于別人的,有幾點(diǎn)差別需要提一下:
- 在間距計(jì)算方面叫挟,別人都是使用固定間距艰匙,這樣會(huì)造成一個(gè)問題是頁面右邊距過大或過小,影響美觀抹恳;我使用的是固定最小間距员凝,通過最小間距計(jì)算出實(shí)際間距。保證每個(gè)元素之間的間距都是相等的奋献。
- 在實(shí)現(xiàn)的時(shí)候健霹,考慮到了滾動(dòng)條的寬度問題,使用自定義的函數(shù)獲取到了滾動(dòng)條的寬度并在實(shí)際計(jì)算中減去滾動(dòng)條寬度瓶蚂。所以始終左右間距保持一致糖埋。
// 定義瀑布流算法函數(shù)
function fall() {
const minGap = 20; // 最小間距,讓每一列的最小空隙可以自定義窃这,避免太過擁擠的情況發(fā)生瞳别。但是,會(huì)通過計(jì)算得到真實(shí)的間距。
const itemWidth = 100; // 每一項(xiàng)的寬度祟敛,即當(dāng)前每一個(gè)圖片容器的寬度疤坝。保證每一列都是等寬不等高的。
const scrollBarWidth = getScrollbarWidth();
const pageWidth = window.innerWidth - scrollBarWidth; // 獲取當(dāng)前頁面的寬度 = window.innerWidth - 滾動(dòng)條的寬度
const column = Math.floor(pageWidth / (itemWidth + minGap)); // 實(shí)際列數(shù)
const gap = (pageWidth - itemWidth * column) / column / ((column + 1) / column); // 計(jì)算真實(shí)間距
const items = document.querySelectorAll('li'); // 獲取所有的外層元素
const heightArr = []; // 定義一個(gè)空數(shù)組馆铁,保存最低高度跑揉。
for (let i = 0; i < items.length; i++) {
// 遍歷所有的外層容器
const height = items[i].offsetHeight;
// 如果當(dāng)前處在第一列
if (i < column) {
// 直接設(shè)置元素距離上部的位置和距離左邊的距離。
items[i].style.cssText = `top: ${gap}px;left: ${(itemWidth + gap) * i + gap}px`;
// 保存當(dāng)前元素的高度叼架。
heightArr.push(height);
} else {
// 不是第一列的話畔裕,就進(jìn)行比對(duì)。
let minHeight = heightArr[0]; // 先保存第一項(xiàng)的高度
let minIndex = 0; // 保存第一項(xiàng)的索引值
for (let j = 0; j < heightArr.length; j++) {
// 通過循環(huán)遍歷比對(duì)乖订,拿到最小值和最小值的索引。
if (minHeight > heightArr[j]) {
minHeight = heightArr[j];
minIndex = j;
}
}
// 通過最小值為當(dāng)前元素設(shè)置top值具练,通過索引為當(dāng)前元素設(shè)置left值乍构。
items[i].style.cssText = `top: ${minHeight + gap *2}px; left: ${(itemWidth + gap) * minIndex + gap}px`;
// 并修改當(dāng)前索引的高度為當(dāng)前元素的高度
heightArr[minIndex] = minHeight + gap + height;
}
}
}
// 頁面加載完成調(diào)用一次。
window.onload = fall;
// 頁面尺寸發(fā)生改變?cè)俅握{(diào)用扛点。
window.onresize = fall;
// 獲取滾動(dòng)條的寬度
function getScrollbarWidth() {
const oDiv = document.createElement('div');//創(chuàng)建一個(gè)div
// 給div設(shè)置樣式哥遮。隨便定義寬高,只要能獲取到滾動(dòng)條就可以
oDiv.style.cssText = `width: 50px;height: 50px;overflowY: scroll;`
document.body.appendChild(oDiv);//把div添加到body中
const scrollbarWidth = oDiv.offsetWidth - oDiv.clientWidth;// 使最大寬度和可視寬度相減陵究,獲得到滾動(dòng)條寬度眠饮。
oDiv.remove();//移除創(chuàng)建的div
return scrollbarWidth;//返回滾動(dòng)條寬度
}
實(shí)現(xiàn)效果:
如果本文對(duì)您有幫助,可以看看本人的其他文章:
原生JS - 圖片懶加載@郝晨光
Koa - Node.js框架學(xué)習(xí)@郝晨光
前端常見面試題(十二)@郝晨光