老項目優化第二步——資源的懶加載和預加載
之前做前端項目性能優化的時候,為了使頁面加載更快,用盡了手段,包括檔案的合并、壓縮,檔案緩存和開啟伺服器端的 gzip 壓縮等等。
但發現壓縮總有個極限,并且遇到弱網環境時,使用者在感覺上依舊會有“慢”的概念,尤其是在首屏展示的時候,于是就選了兩種方案(骨架屏和資源預加載,骨架屏下次再總結)。
預加載
預加載相信很多人都有聽說過,引用Patrick Hamann的解釋就是,預加載是浏覽器對将來可能被使用資源的一種暗示,一些資源可以在目前頁面使用到,一些可能在将來的某些頁面中被使用。
簡單了解就是,将頁面加載時的資源提前加載到本地,等頁面真正加載時直接從緩存擷取資源。
我主要是使用在首屏加載時需要的資源,以及一個資源過大的檔案上,避免頁面長時間空白,減少等待時間,優化體驗。
預加載可以細分為以下幾個點:DNS-prefetch、subresource 、 prefetch、preconnect、prerender。
可根據自己項目需要去使用。
(1)DNS 預解析 DNS-Prefetch
通過 DNS 預解析來告訴浏覽器未來我們可能從某個特定的 URL 擷取資源,當浏覽器真正使用到該域中的某個資源時就可以盡快地完成 DNS 解析。比如說,我們可能從 http://example.com 擷取資源時(圖檔、音頻等),就可以在<head> 标簽中加入以下内容:
<link rel="dns-prefetch" href="//example.com" target="_blank" rel="external nofollow" >
當我們從該 URL 請求一個資源時,就不再需要等待 DNS 的解析過程。
(2)預連接配接 Preconnect
與 DNS 預解析類似,preconnect 不僅完成 DNS 預解析,同時還将進行 TCP 握手和建立傳輸層協定。可以這樣使用:
<link rel="preconnect" href="http://example.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >
(3)預擷取 Prefetching
如果我們确定某個資源将來一定會被使用到,我們可以讓浏覽器預先請求該資源并放入浏覽器緩存中,也就是說如果我們猜測使用者接下來将要通路哪個具體的資源,那就可以用prefetching來預加載确定的資源了。
<link rel="prefetch" href="image.png" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >
(4)Subresources
這是另一個預擷取方式,這種方式指定的預擷取資源具有最高的優先級,在所有 prefetch 項之前進行:
<link rel="subresource" href="styles.css" target="_blank" rel="external nofollow" >
prefetch 為将來的頁面提供了一種低優先級的資源預加載方式,而subresource 為目前頁面提供了一種高優先級的資源預加載。
如果資源是目前頁面必須的,或者資源需要盡快可用,那麼最好使用 subresource 而不是 prefetch。
(5)預渲染 Prerender
prerender 可以預先加載文檔的所有資源,類似于在隐藏的tab 頁中打開了某個連結 – 将下載下傳所有資源、建立 DOM 結構、完成頁面布局、應用 CSS 樣式和執行 JavaScript 腳本等。
當使用者真正通路該連結時,隐藏的頁面就切換為可見,使頁面看起來就是瞬間加載完成一樣。
<link rel="prerender" href="http://example.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >
需要注意的是不要濫用該特性,當你知道使用者一定會點選某個連結時才可以進行預渲染,否則浏覽器将無條件地下載下傳所有預渲染需要的資源。
(6)新特性:Preloading
和prefetching不同,preloading會讓浏覽器無論如何都下載下傳指定的資源,也就是說浏覽器一定會預加載該資源。
<link rel="preload" href="image.png" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >
所有預加載技術都存在一個潛在的風險:對資源預測錯誤。
而預加載的開銷是高昂的,比如搶占 CPU 資源,消耗電池,浪費帶寬等,是以必須謹慎行事。
雖然很難确定使用者下一步将通路哪些資源,但高可信的場景确實存在:
- 如果使用者完成一個帶有明顯結果的搜尋,那麼結果頁面很可能會被加載。
- 如果使用者進入到登陸頁面,那麼登陸成功的頁面很可能會被加載。
- 如果使用者閱讀一個多頁的文章或通路一個分頁的結果集,那麼下一頁很可能會被加載。
(7)H5音樂預加載 preload=”auto”
<audio src="audio.mp3" autoplay="autoplay" loop preload="auto" id="sendid2"></audio>
(8)使用html标簽
<img src="image.png" style="display:none"/>
(9)使用Image對象
<script src="./imagePreload.js"></script>
// imagePreload.js檔案
var image= new Image()
image.src="https://xxx.xx.com/image.jpg"
優化網頁性能的另一種方式, 懶加載 。
圖檔懶加載圖檔懶加載一般用于網頁中延遲加載圖像,使用者滾動到它們之前,可視區域外的圖像不會加載。這與圖像預加載相反,在長網頁上使用延遲加載将使網頁加載更快。
使用懶加載是為了應對那些頁面過長,圖檔資源過多的網頁。如果要等整個頁面一次性加載完成,那體驗就太差了。
實作原理- 首先,不要将圖檔位址放到src屬性中,而是放到其它屬性(data-original)中。
- 頁面加載完成後,根據scrollTop判斷圖檔是否在使用者的視野内,如果在,則将data-original屬性中的值取出存放到src屬性中。
- 在滾動事件中重複判斷圖檔是否進入視野,如果進入,則将data-original屬性中的值取出存放到src屬性中。
// 圖檔懶加載
<html >
<head>
<meta charset="UTF-8">
<title>Lazyload</title>
<style>
.image-item {
display: block;
margin-bottom: 50px;
height: 200px; // 一定記得設定圖檔高度
}
</style>
</head>
<body>
<img src="" class="image-item" lazyload="true" data-original="images/1.png" />
<img src="" class="image-item" lazyload="true" data-original="images/2.png" />
<img src="" class="image-item" lazyload="true" data-original="images/3.png" />
<img src="" class="image-item" lazyload="true" data-original="images/4.png" />
<img src="" class="image-item" lazyload="true" data-original="images/5.png" />
<img src="" class="image-item" lazyload="true" data-original="images/6.png" />
<img src="" class="image-item" lazyload="true" data-original="images/7.png" />
<img src="" class="image-item" lazyload="true" data-original="images/8.png" />
<img src="" class="image-item" lazyload="true" data-original="images/9.png" />
<img src="" class="image-item" lazyload="true" data-original="images/10.png" />
<img src="" class="image-item" lazyload="true" data-original="images/11.png" />
<img src="" class="image-item" lazyload="true" data-original="images/12.png" />
<script>
var viewHeight = document.documentElement.clientHeight // 擷取可視區高度
function lazyload() {
var eles = document.querySelectorAll('img[data-original][lazyload]')
Array.prototype.forEach.call(eles, function (item, index) {
var rect
if (item.dataset.original === "")
return
rect = item.getBoundingClientRect() // 用于獲得頁面中某個元素的左,上,右和下分别相對浏覽器視窗的位置
if (rect.bottom >= 0 && rect.top < viewHeight) {
!function () {
var img = new Image()
img.src = item.dataset.original
img.onload = function () {
item.src = img.src
}
item.removeAttribute("data-original")// 移除屬性,下次不再周遊
item.removeAttribute("lazyload")
}()
}
})
}
lazyload() // 剛開始還沒滾動螢幕時,要先觸發一次函數,初始化首頁的頁面圖檔
document.addEventListener("scroll",lazyload)
</script>
</body>
</html>
懶加載庫
Github上也有很多懶加載庫可以直接引用:
- lazysizes 是一個功能十分強大的懶加載庫,主要用于加載圖檔和iframes。你隻需要指定src/srcset屬性,lazysizes會幫你自動懶加載内容。值得注意的是,lazysizes基于intersection observer,是以你需要一個polyfill。你還可以通過一些插件擴充庫的功能以用于懶加載視訊。
- lozad.js是一個輕量級、高性能的懶加載庫,基于intersection observer,你同樣需要提供一個相關的polyfill。
- blazy是一個輕量級的懶加載庫,大小僅為1.4KB。相對于lazysizes,它不需要任何的外部依賴,并且相容IE7+。你可能猜測到了,blazy不支援intersection observer,性能相對較差。
- react-lazyload基于react的懶加載元件。
Vue路由懶加載(元件按需加載)
在單頁應用中,運用webpack打包後的檔案随着項目的進行會變得很大,元件越來越多,首次啟動項目時,需要加載的内容過多,延時過長,不利于使用者體驗。
運用懶加載可以将頁面進行劃分,需要的時候加載頁面,可以有效的分擔首頁所承擔的加載壓力,減少首頁加載時間。
與webpack配合實作元件懶加載(1)在webpack配置檔案中的output路徑配置chunkFilename屬性
output: {
path: resolve(__dirname, 'dist'),
filename: options.dev ? '[name].js': '[name].js?[chunkhash]',
chunkFilename: 'chunk[id].js?[chunkhash]',
publicPath: options.dev ? '/assets/': publicPath
},
// chunkFilename路徑将會作為元件懶加載的路徑
(2)配合webpack支援的異步加載方法
// resolve => require([URL], resolve), 支援性好
// () => system.import(URL) , webpack2官網上已經聲明将逐漸廢除, 不推薦使用
// () => import(URL), webpack2官網推薦使用, 屬于es7範疇, 需要配合babel的syntax-dynamic-import插件使用, 具體使用方法如下
npm install --save-dev babel-core babel-loader babel-plugin-syntax-dynamic-importbabel-preset-es2015
use: [{
loader: 'babel-loader',
options: {
presets: [['es2015', {modules: false}]],
plugins: ['syntax-dynamic-import']
}
}]
具體執行個體中實作懶加載 (1)路由中配置異步元件
export default new Router({
routes: [
{
mode: 'history',
path: '/home,
name: home,
component: resolve => require(['../views/home.vue'], resolve) // 懶加載
},
]
})
(2)執行個體中配置異步元件
components: {
historyTab: resolve => {require(['../../component/page.vue'], resolve)} // 懶加載
}
(3)全局注冊異步元件
Vue.component(‘header’, () => {
System.import('./component/header.vue')
})
以上是這次項目優化在加載這方面整理的材料,後續會持續更新歡迎各位朋友指正