六月份開始搭建的自己的第一個網站:書蟲小說。
在經歷前端頁面重寫後,網站首屏加載速度有了一定提升,之後發現小說目錄詳情頁白屏時間竟然高達1-2s,拖了一段時間,打算解決這個問題,我用Chrome調試抓包發現json數據包大小竟然有200多KB,測試了一下加載時間大概在700ms-1500ms,由於章節列表的生成依賴於這個數據,所以自然會產生很長一段時間的白屏,我打算先解決這個問題。
server傳數據是支持gzip壓縮的,例如nginx發送html文檔給客戶端,會自動開啓gzip壓縮。至於一些其他格式的數據比如json需要手動開啓,修改api網站nginx配置文件如下:
gzip on;
# 設置最小壓縮大小,防止出現越壓越大的情況
gzip_min_length 1k;
# 代表壓縮級別,1-9可選,9最高同時也最耗cpu(但是好像沒關係目測了下我CPU很佛系,平時佔用超低)
gzip_comp_level 9;
gzip_proxied expired no-cache no-store private auth;
# 代表要壓縮的數據類型
gzip_types text/plain application/x-javascript text/css
application/xml application/javascript application/json;
經過這個設置後,json數據從200+KB降到了30+KB,加載時間從原來的700+ms
降到了200+ms, 白屏時間至少減少了1s,可是依然存在。
這時我想到我要加載的是一個長列表,很多小說的章節數高達1000+,有一部《校花的貼身高手》更是高達8000+章,要一次渲染一個這麼長的列表是相當耗費性能的,所以決定採用懶加載方案。
方案詳情:
1. 數據還是一次性請求,因爲數據延遲時間已經可以接受了,分多次請求的反而還增加了服務器壓力(重新請求又要建立TCP連接,又要進行三次握手,blablabla...)。
2. 初次載入時只加載部分章節,直到用戶滑到網頁文檔底部時開始插入數據,由於是加載已經緩存在內存中的數據,所以幾乎不會有延遲,只是會因重新插入節點頁面迴流。
如何判斷一個元素是否滾動到底?
var element = document.documentElement //這裏拿document來判斷
element.scrollHeight - element.scrollTop === element.clientHeight
幾個屬性及其含義:
- scrollHeight
表示的是元素的內容高度,包括了視區不可見的內容
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-07mEcw4a-1572932371223)(https://developer.mozilla.org/@api/deki/files/840/=ScrollHeight.png)]
- scrollTop
一個元素的 scrollTop 值是這個元素的頂部到視口可見內容(的頂部)的距離的度量。當一個元素的內容沒有產生垂直方向的滾動條,那麼它的 scrollTop 值爲0。
如圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zEYuzPCe-1572932371227)(https://developer.mozilla.org/@api/deki/files/842/=ScrollTop.png)]
- clientHeight
元素在出現在視區內的高度
有了這些概念,所以當一個滾動元素滑倒底部時,那麼element.scrollHeight - element.scrollTop 應該等於 element.clientHeight
我的做法是給documentElement添加了scroll事件處理器,監聽滾動事件,由於頻繁獲取以上這幾個屬性又挺消耗性能,我給回調函數設置了節流
scrollWatcher() { // method
throttle(() => {
let el = document.documentElement;
//這裏再減300是爲了預先加載,如果等到真的滑倒底部再加載時機有點晚
let flag = el.scrollHeight - el.scrollTop - 300 <= el.clientHeight;
if (flag) {
// console.log(flag)
this.offset += 1;
}
}, 400)();
}
chapters() { // computed
//this.getCatalog.chapters存放的是全部的數據,
//每一次快要滑倒底部事件的觸發,就讓offset + 1,
//利用數組的切片方法不斷更新這個區間,直到全部拿完,
//也就是所有章節在頁面加載完畢,移除這個事件監聽器
const chapters = this.getCatalog.chapters.slice(0, this.offset * 60 + 60);
if (this.chaptersLen === chapters.length) {
document.removeEventListener('scroll', this.scrollWatcher);
}
return chapters;
}