文章不短(我寫的很詳細(xì)) 耐心看完 給你一個極好性能的瀑布流及圖片懶加載
瀑布流效果
何為瀑布流颂鸿?
瀑布流,又稱瀑布流式。是比較流行的一種網(wǎng)站頁面布局问顷,視覺表現(xiàn)為參差不齊的多欄布局,隨著頁面滾動條向下滾動禀梳,這種布局還會不斷加載數(shù)據(jù)塊并附加至當(dāng)前尾部杜窄。最早采用此布局的網(wǎng)站是Pinterest,逐漸在國內(nèi)流行開來算途。國內(nèi)大多數(shù)清新站基本為這類風(fēng)格塞耕。
即多行等寬元素排列,后面的元素依次添加到其后嘴瓤,等寬不等高扫外,根據(jù)圖片原比例縮放直至寬度達(dá)到我們的要求,依次按照規(guī)則放入指定位置廓脆。
那么規(guī)則是什么呢筛谚?
下面通過圖解來分析一下瀑布流的算法。
圖解瀑布流算法
當(dāng)?shù)谝慌排艥M足夠多的等寬圖片時(如下圖情況)停忿,自然而然的考慮到之后放置的圖片會往下面排放驾讲。
那么第六張圖片,放置在什么位置呢?是下圖的位置么蝎毡?
答案是:不是厚柳,那我們應(yīng)該怎樣排列呢?
為了減小每列的差距我們應(yīng)該將后面六張圖片中最高的那一個放到當(dāng)前這六列中最矮的那一列沐兵,正數(shù)第二高的圖片放到倒數(shù)第二矮的那一列......以此類推
- 好了别垮,實現(xiàn)的思想大家已經(jīng)看了,接下來咱們用代碼實現(xiàn)一個三列的瀑布流
實現(xiàn)代碼
- 先看HTML扎谎、CSS
- HTML
<body>
<div class="container clearfix">
<div class="column">
<!-- <div class="card">
<a href="#">
<div class="lazyImageBox">
<img src="" alt="" data-image="images/1.jpg">
</div>
<p>泰勒·斯威夫特(Taylor Swift)碳想,1989年12月13日出生于美國賓州,美國歌手毁靶、演員胧奔。2006年出道,同年發(fā)行專輯《泰勒·斯威夫特》预吆,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證</p>
</a>
</div> -->
</div>
<div class="column"></div>
<div class="column"></div>
</div>
</body>
- CSS
html,
body {
background: #D6D7DB;
}
.container {
box-sizing: border-box;
margin: 20px auto;
width: 760px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.container .column {
box-sizing: border-box;
width: 240px;
}
.card {
margin-bottom: 10px;
padding: 5px;
background: #FFF;
box-shadow: 3px 3px 10px 0 #222;
}
.card a {
display: block;
}
.card a .lazyImageBox {
overflow: hidden;
}
.card a .lazyImageBox img {
width: 100%;
}
.card a p {
margin-top: 5px;
color: #000;
font-size: 12px;
line-height: 20px;
}
- 創(chuàng)造了一個寬度為760px的容器 使用css3彈性盒使內(nèi)容兩端對齊然后將內(nèi)容分為3列龙填,每列240 像素
- 獲取數(shù)據(jù)
- 模擬數(shù)據(jù)(創(chuàng)建data.json)
<data.json>
[
{
"id": 1,
"pic": "images/9.jpg",
"width": 300,
"height": 433,
"title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州拐叉,美國歌手岩遗、演員。2006年出道凤瘦,同年發(fā)行專輯《泰勒·斯威夫特》宿礁,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
{
"id": 2,
"pic": "images/5.jpg",
"width": 300,
"height": 200,
"title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州蔬芥,美國歌手梆靖、演員。2006年出道笔诵,同年發(fā)行專輯《泰勒·斯威夫特》返吻,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
{
"id": 3,
"pic": "images/3.jpg",
"width": 300,
"height": 170,
"title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州乎婿,美國歌手思喊、演員。2006年出道次酌,同年發(fā)行專輯《泰勒·斯威夫特》恨课,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
{
"id": 4,
"pic": "images/2.jpg",
"width": 300,
"height": 300,
"title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州岳服,美國歌手剂公、演員。2006年出道吊宋,同年發(fā)行專輯《泰勒·斯威夫特》纲辽,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
{
"id": 5,
"pic": "images/3.jpg",
"width": 300,
"height": 170,
"title": "泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美國賓州,美國歌手拖吼、演員鳞上。2006年出道,同年發(fā)行專輯《泰勒·斯威夫特》吊档,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
{
"id": 6,
"pic": "images/10.jpg",
"width": 300,
"height": 257,
"title": "泰勒·斯威夫特(Taylor Swift)篙议,1989年12月13日出生于美國賓州,美國歌手怠硼、演員鬼贱。2006年出道,同年發(fā)行專輯《泰勒·斯威夫特》香璃,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
{
"id": 7,
"pic": "images/6.jpg",
"width": 300,
"height": 400,
"title": "泰勒·斯威夫特(Taylor Swift)这难,1989年12月13日出生于美國賓州,美國歌手葡秒、演員姻乓。2006年出道,同年發(fā)行專輯《泰勒·斯威夫特》眯牧,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
{
"id": 8,
"pic": "images/5.jpg",
"width": 300,
"height": 200,
"title": "泰勒·斯威夫特(Taylor Swift)蹋岩,1989年12月13日出生于美國賓州,美國歌手炸站、演員星澳。2006年出道疚顷,同年發(fā)行專輯《泰勒·斯威夫特》旱易,該專輯獲得美國唱片業(yè)協(xié)會的白金唱片認(rèn)證",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
}
]
- 創(chuàng)建utils.js
- 簡單封裝ajax
<utils.js>
class utils {
constructor() {
console.log('工具類');
}
/**
* 封裝Ajax
* @param {*} url
* @returns {Promise}
*/
ajax (url) {
return new Promise(resolve => {
let xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.onreadystatechange = function () {
(xhr.readyState === 4 && xhr.status === 200) && resolve(JSON.parse(xhr.responseText));
};
xhr.send();
});
}
}
- 使用ajax獲取數(shù)據(jù)并渲染頁面 注意看注釋
let data = await utils.ajax('./data.json');
bindHTML(data);
// 獲取到所有class為column的元素并將類數(shù)組轉(zhuǎn)化為數(shù)組實例
let columns = Array.from(document.querySelectorAll('.column'));
/**
* 數(shù)據(jù)綁定
* @param {Array} data
*/
function bindHTML (data) {
// 根據(jù)服務(wù)器返回的圖片的寬高,動態(tài)計算出圖片放在230容器中腿堤,高度應(yīng)該怎么縮放
// 因為我們后期要做圖片的延遲加載阀坏,在沒有圖片之前,我們也需要知道未來圖片要渲染的高度笆檀,這樣才能又一個容器先占位
// 根據(jù)容器寬度(230)等比縮放數(shù)據(jù)中的圖片
data.forEach(item => {
let { width, height } = item;
item.height = height / (width / 230);
item.width = 230;
})
// 每三個為一組獲取數(shù)據(jù)
for (let i = 0; i < data.length; i += 3) {
// 將數(shù)組分為3列一組
let group = data.slice(i, i + 3);
// 獲取到當(dāng)前每一列的高度并進(jìn)行升序處理
columns.sort((a, b) => b.offsetHeight - a.offsetHeight);
// 把每一組的數(shù)據(jù)按照圖片高度進(jìn)行降序處理
group.sort((a, b) => a.height - b.height);
// 把告訴最小的圖片插入到高度最大的列中 (看不懂繼續(xù)看圖解)
group.forEach((item, index) => {
let { height, title, pic } = item;
// 創(chuàng)建存放圖片的容器
let card = document.createElement('div');
card.className = "card";
// 使用字符模板(es6)為容器添加內(nèi)容
card.innerHTML = `<a href="#">
<div class="lazyImageBox" style="height:${height}px">
<img src="${pic}" alt="">
</div>
<p>${title}</p>
</a>`;
// 向每一列中添加數(shù)據(jù)
columns[index].appendChild(card);
});
}
}
- 至此我們實現(xiàn)了瀑布流---接下來我們將實現(xiàn)圖片的懶加載
/*
* 為啥要做圖片的延遲加載
* 瀏覽器渲染頁面
* 1.構(gòu)建DOM樹
* 2.構(gòu)建CSSOM樹
* 3.生成RENDER TREE (渲染樹)
* 4.布局
* 5.分層
* 6.珊格化
* 7.繪制
* 構(gòu)建DOM樹中如果遇到img
* 老版本瀏覽器:阻礙DOM渲染
* 新版本瀏覽器:不會阻礙 每一個圖片請求都會占用一個HTTP(瀏覽器同時發(fā)送的HTTP 6個)
* 拿回來資源后會和RENDER TREE一起渲染
* .....
* 開始加載圖片忌堂,一定會讓頁面第一次渲染速度變慢(白屏)
*
* 圖片延遲加載:第一次不請求也不渲染圖片,等頁面加載完酗洒,其他資源都渲染好了士修,再去請求加載圖片
*/
懶加載的優(yōu)點
- 增強(qiáng)用戶體驗
- 優(yōu)化代碼
- 減少http的請求
- 減少服務(wù)器端壓力
- 服務(wù)器的按需加載
懶加載的原理
先將img標(biāo)簽中的src鏈接設(shè)為同一張圖片(空白圖片),將其真正的圖片地址存儲再img標(biāo)簽的自定義屬性中(比如data-src)樱衷。當(dāng)js監(jiān)聽到該圖片元素進(jìn)入可視窗口時棋嘲,即將自定義屬性中的地址存儲到src屬性中,達(dá)到懶加載的效果矩桂。
這樣做能防止頁面一次性向服務(wù)器響應(yīng)大量請求導(dǎo)致服務(wù)器響應(yīng)慢沸移,頁面卡頓或崩潰等問題。
- 接下來我將使用 getBoundingClientRect方法實現(xiàn)圖片大的懶加載
getBoundingClientRect 方法兼容性較好 但不是唯一的解決方案 在文章最后我會簡單介紹幾種其他的幾種方案
- 首先我們來了解下 getBoundingClientRect
理解:getBoundingClientRect用于獲取某個元素相對于視窗的位置集合。集合中有top, right, bottom, left等屬性雹锣。
返回值類型
- top:元素上邊到視窗頂部的距離;
- right:元素右邊到視窗頂部的距離;
- bottom:元素下邊到視窗頂部的距離;
- left:元素左邊到視窗頂部的距離;
-
圖解
接下來我們使用代碼實現(xiàn)圖片的懶加載 注意看注釋
// 創(chuàng)建一個對象存放需要加載的圖片列表
let lazyImageBoxs;
// 獲取瀏覽器可視區(qū)域高度
let winH = document.documentElement.clientHeight;
function lazyFunc () {
!lazyImageBoxs ? lazyImageBoxs = Array.from(document.querySelectorAll('.lazyImageBox')) : null;
lazyImageBoxs.forEach(lazyImageBox => {
// 已經(jīng)處理過則不在處理 詳情請看 lazyImg 函數(shù)
let isLoad = lazyImageBox.getAttribute('isLoad');
if (isLoad) return;
// 若圖片底部距離瀏覽器頂部的距離小于瀏覽器的可視區(qū)域高度 那么說明此圖片已完全顯示出來
let { bottom } = lazyImageBox.getBoundingClientRect();
bottom <= winH && lazyImg(lazyImageBox);
});
}
/**
* 懶加載圖片
* @param {dom} lazyImageBox
*/
function lazyImg (lazyImageBox) {
// img:獲取到當(dāng)前元素中的圖片网沾,trueImg:需要渲染的圖片路徑
let img = lazyImageBox.querySelector('img'),
trueImg = img.getAttribute('data-image');
// 加載圖片
img.src = trueImg;
// 移除自定義屬性
img.removeAttribute('data-image');
// 添加isLoad屬性:表示當(dāng)前圖片已經(jīng)處理過了
lazyImageBox.setAttribute('isLoad', 'true');
}
// 當(dāng)頁面渲染完成或滾動時都執(zhí)行圖片懶加載函數(shù)
window.onload = lazyFunc;
window.onscroll = lazyFunc;
- 至此我們就實現(xiàn)了圖片的懶加載
問題:注意看以下兩行代碼
window.onload = lazyFunc;
window.onscroll = lazyFunc;
這樣的弊端是什么
onscroll觸發(fā)的頻率太高了,滾動一下可能要被觸發(fā)很多次蕊爵,導(dǎo)致很多沒必要的計算和處理辉哥,消耗性能
- 演示
我們在以下函數(shù)中 添加console.log('OK'); 看頁面滾動的時候會觸發(fā)多少次
function lazyImg (lazyImageBox) {
console.log('OK');
//......
}
接下來我們看控制臺的輸出
這是我滑動滾輪5次函數(shù)執(zhí)行的次數(shù),看到這個數(shù)字你是否為頁面的性能感到焦慮在辆,那么應(yīng)該如何處理這種問題呢证薇?
這就用到了 函數(shù)節(jié)流
接下來我們在utils 工具類中寫一個節(jié)流方法
class utils {
constructor() {
console.log('工具類');
}
/*
* throttle:實現(xiàn)函數(shù)的節(jié)流(目的是頻繁觸發(fā)中縮減頻率)
* @params
* func:需要執(zhí)行的函數(shù)
* wait:自己設(shè)定的間隔時間(頻率)
* @return
* 可被調(diào)用執(zhí)行的函數(shù)
*/
throttle (func, wait = 500) {
let timer = null,
previous = 0; //記錄上一次操作時間
return function anonymous (...params) {
let now = new Date(), //當(dāng)前操作的時間
remaining = wait - (now - previous);
if (remaining <= 0) {
// 兩次間隔時間超過頻率:把方法執(zhí)行即可
clearTimeout(timer);
timer = null;
previous = now;
func.call(this, ...params);
} else if (!timer) {
// 兩次間隔時間沒有超過頻率,說明還沒有達(dá)到觸發(fā)標(biāo)準(zhǔn)呢匆篓,設(shè)置定時器等待即可(還差多久等多久)
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(this, ...params);
}, remaining);
}
};
}
/**
* 封裝Ajax
* @param {*} url
* @returns {Promise}
*/
ajax (url) {
return new Promise(resolve => {
let xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.onreadystatechange = function () {
(xhr.readyState === 4 && xhr.status === 200) && resolve(JSON.parse(xhr.responseText));
};
xhr.send();
});
}
}
- 最后來so easy啦
window.onload = lazyFunc;
window.onscroll = utils.throttle(lazyFunc, 500);
-
讓我們看下節(jié)流后的效果
我們可以看到同樣是滾動5次 函數(shù)的執(zhí)行次數(shù)由149 次變?yōu)?1次
其他懶加載方式:
- new IntersectionObserver
- 直接為img 添加loading="lazy"屬性:目前只有Chrome64 版本已上才兼容 感覺這將是未來的趨勢
具體實現(xiàn)方法我就不一一寫出來了浑度,大家有興趣的可以了解下,這兩種方法比現(xiàn)在用的這種方法要好美中不足的是兼容性還不行