Contents
- ## Contents
- ## 前言
- ## 性能優(yōu)化指標(biāo)
- ## 性能測(cè)試工具 lighthouse
- ## 基于 hexo 框架的網(wǎng)站優(yōu)化
- ## 參考
- ## 結(jié)語(yǔ)
前言
1. hexo 是什么练链?
hexo 是一個(gè)為了不依賴于后端進(jìn)行界面實(shí)時(shí)數(shù)據(jù)查詢展示而設(shè)計(jì)的網(wǎng)站開發(fā)工具携丁。比如之前曾使用 Node.js 作為后臺(tái)開發(fā)過一個(gè)博客網(wǎng)站,自己實(shí)現(xiàn)后臺(tái)邏輯的話需要考慮數(shù)據(jù)庫(kù)存儲(chǔ)焚碌、前后端交互畦攘、后端 Server 部署啥的。整個(gè)流程比較繁雜十电,在初期可以作為前端開發(fā)者個(gè)人建站學(xué)習(xí)的一種方式念搬。hexo 簡(jiǎn)化了這一流程,它將數(shù)據(jù)存儲(chǔ)和數(shù)據(jù)獲取這兩方面都通過編譯構(gòu)建然后本地化集成到前端靜態(tài)資源里摆出。一個(gè)例子就是博客網(wǎng)站通常需要翻頁(yè)功能來(lái)獲取博客文章,傳統(tǒng)開發(fā)方式下首妖,獲取下一頁(yè)這個(gè)操作由前端腳本發(fā)起袖外,并由后端 Server 處理請(qǐng)求并返回,但是使用 hexo 之后這整個(gè)過程都是在本地一次完成的,hexo 將所有靜態(tài)資源在本地建立了索引。
使用 hexo 通常寫手只需要關(guān)注 markdown 格式文章的編寫,其余的網(wǎng)站編譯、構(gòu)建和發(fā)布的流程都可以交由框架進(jìn)行處理,所有的網(wǎng)站內(nèi)容均會(huì)被打包成靜態(tài)的 html/css/js 文件。hexo 支持自定義插件身腻,也有一個(gè)插件社區(qū)产还,如果寫手同時(shí)具備前端能力的話也可以發(fā)布自己的插件到社區(qū)里進(jìn)行開源共享。
2. 何為優(yōu)化嘀趟?
我們通常所講的性能高低可能側(cè)重于對(duì)網(wǎng)站運(yùn)行速度快慢的評(píng)估脐区,其包括靜態(tài)資源及腳本獲取的速度和網(wǎng)站UI界面是否運(yùn)行流暢。其實(shí)廣義上的優(yōu)化應(yīng)包括:網(wǎng)站性能優(yōu)化她按、網(wǎng)站可訪性優(yōu)化牛隅、網(wǎng)站SEO優(yōu)化、網(wǎng)站最佳實(shí)踐等酌泰。
性能優(yōu)化指標(biāo)
1. 整體運(yùn)行性能
FCP (First Contentful Paint):從用戶開始發(fā)起網(wǎng)站請(qǐng)求到瀏覽器第一次開始渲染網(wǎng)站數(shù)據(jù)的所用時(shí)長(zhǎng)媒佣。其中提及的第一次開始渲染的網(wǎng)站數(shù)據(jù)包含網(wǎng)頁(yè)的文字、圖片陵刹、HTML DOM 結(jié)構(gòu)等默伍,而不包含位于 iframe 中的網(wǎng)頁(yè)數(shù)據(jù)。該指標(biāo)通常用于衡量本地和服務(wù)器首次建立網(wǎng)絡(luò)通訊的速度衰琐。
TTI (Time To Interactive):從用戶開始導(dǎo)航至網(wǎng)站到頁(yè)面變?yōu)橥耆山换ニㄙM(fèi)的時(shí)間也糊。網(wǎng)站可交互的衡量標(biāo)準(zhǔn)就是:網(wǎng)站展示了實(shí)際可用的內(nèi)容、界面上可見元素的網(wǎng)頁(yè)事件已經(jīng)被成功綁定(比如點(diǎn)擊羡宙、拖動(dòng)等事件)狸剃、用戶和頁(yè)面交互的反饋時(shí)間低于 50 ms。
SI (Speed Index):衡量頁(yè)面加載過程中內(nèi)容可視化顯示的速度狗热。通俗來(lái)講就是網(wǎng)站界面元素的繪制和呈現(xiàn)速度钞馁,如果使用 lighthouse 測(cè)量工具的話它會(huì)捕獲瀏覽器中處于加載中的頁(yè)面的多個(gè)圖片幀,然后計(jì)算幀之間的視覺渲染進(jìn)度匿刮。
TBT (Total Blocking Time):衡量從頁(yè)面首次開始渲染(FCP)之后到頁(yè)面實(shí)際可交互(TTI)的時(shí)間僧凰。通常我們?cè)L問一個(gè)網(wǎng)站時(shí)網(wǎng)站整體呈現(xiàn)后,有一段較短的時(shí)間我們不能和界面進(jìn)行交互熟丸,比如鼠標(biāo)點(diǎn)擊训措、鍵盤按鍵等,這段時(shí)間瀏覽器在進(jìn)行腳本及樣式的加載和執(zhí)行虑啤。
LCP (Largest Contentful Paint):測(cè)量視口中最大的內(nèi)容元素何繪制到屏幕所需的時(shí)間。通常包含這個(gè)元素的下載架馋、解析和渲染整個(gè)過程狞山。
CLS (Cumulative Layout Shift):一個(gè)衡量網(wǎng)站加載時(shí)整體布局抖動(dòng)情況的數(shù)值指標(biāo)。如果一個(gè)網(wǎng)站在加載過程中用戶界面多次抖動(dòng)和閃爍的話會(huì)可能引起用戶的輕度不適叉寂,因此應(yīng)該盡量減少網(wǎng)站的重排和重繪次數(shù)萍启。
2. 網(wǎng)站可訪問性
- 網(wǎng)頁(yè)背景色和網(wǎng)站文字前景的對(duì)比度不能太低,否則會(huì)影響用戶閱讀。
- 網(wǎng)頁(yè)鏈接標(biāo)簽
<a>
最好包含對(duì)鏈接的描述信息勘纯,比如:<a >[描述- nojsja 的 github 個(gè)人界面]</a>
局服。 - html 元素存在
lang
屬性指定當(dāng)前語(yǔ)言環(huán)境。 - 正確的 html 語(yǔ)義化標(biāo)簽?zāi)茏屾I盤和讀屏器正常工作驳遵,通常一個(gè)網(wǎng)頁(yè)的結(jié)構(gòu)可以用語(yǔ)義化標(biāo)簽描述為:
<html lang="en">
<head>
<title>Document title</title>
<meta charset="utf-8">
</head>
<body>
<a class="skip-link" href="#maincontent">Skip to main</a>
<h1>Page title</h1>
<nav>
<ul>
<li>
<a >Nav link</a>
</li>
</ul>
</nav>
<header>header</header>
<main id="maincontent">
<section>
<h2>Section heading</h2>
<p>text info</p>
<h3>Sub-section heading</h3>
<p>texgt info</p>
</section>
</main>
<footer>footer</footer>
</body>
</html>
- 界面元素的 id 唯一性淫奔。
- img 標(biāo)簽的
alt
屬性聲明。它指定了替代文本堤结,用于在圖像無(wú)法顯示或者用戶禁用圖像顯示時(shí)唆迁,代替圖像顯示在瀏覽器中的內(nèi)容。 - form 元素內(nèi)部聲明
label
標(biāo)簽以讓讀屏器正確工作竞穷。 - iframe 元素聲明
title
屬性來(lái)描述其內(nèi)部?jī)?nèi)容以便于讀屏器工作唐责。 - aria 無(wú)障礙屬性和標(biāo)簽的使用,相關(guān)參考 >> aria reference
- input[type=image]瘾带、object 標(biāo)簽添加
alt
屬性聲明:
<input type="image" alt="Sign in" src="./sign-in-button.png">
<object alt="report.pdf type="application/pdf" data="/report.pdf">
Annual report.
</object>
- 需要使用 tab 按鍵聚焦特性的元素可以聲明
tabindex
屬性鼠哥,當(dāng)我們按 tab 鍵時(shí)焦點(diǎn)會(huì)依次切換。并且根據(jù)鍵盤序列導(dǎo)航的順序看政,值為 0 朴恳、非法值、或者沒有 tabindex 值的元素應(yīng)該放置在 tabindex 值為正值的元素后面:
1) tabindex=負(fù)值 (通常是tabindex=“-1”)帽衙,表示元素是可聚焦的菜皂,但是不能通過鍵盤導(dǎo)航來(lái)訪問到該元素,用JS做頁(yè)面小組件內(nèi)部鍵盤導(dǎo)航的時(shí)候非常有用厉萝。
2) tabindex="0" 恍飘,表示元素是可聚焦的,并且可以通過鍵盤導(dǎo)航來(lái)聚焦到該元素谴垫,它的相對(duì)順序是當(dāng)前處于的DOM結(jié)構(gòu)來(lái)決定的章母。
3) tabindex=正值,表示元素是可聚焦的翩剪,并且可以通過鍵盤導(dǎo)航來(lái)訪問到該元素乳怎;它的相對(duì)順序按照tabindex 的數(shù)值遞增而滯后獲焦。如果多個(gè)元素?fù)碛邢嗤?tabindex前弯,它們的相對(duì)順序按照他們?cè)诋?dāng)前DOM中的先后順序決定蚪缀。
- table 元素中正確使用
th
和scope
讓行表頭和列表頭與其數(shù)據(jù)域一一對(duì)應(yīng):
<table>
<caption>My marathon training log</caption>
<thead>
<tr>
<th scope="col">Week</th>
<th scope="col">Total miles</th>
<th scope="col">Longest run</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>14</td>
<td>5</td>
</tr>
<tr>
<th scope="row">2</th>
<td>16</td>
<td>6</td>
</tr>
</tbody>
</table>
- video 元素指定
track
文本字幕資源以方便聽障人士使用(需要有字幕資源文件):
<video width="300" height="200">
<source src="marathonFinishLine.mp4" type="video/mp4">
<track src="captions_en.vtt" kind="captions" srclang="en" label="english_captions">
<track src="audio_desc_en.vtt" kind="descriptions" srclang="en" label="english_description">
<track src="captions_es.vtt" kind="captions" srclang="es" label="spanish_captions">
<track src="audio_desc_es.vtt" kind="descriptions" srclang="es" label="spanish_description">
</video>
- li 列表標(biāo)簽放在容器組件
ul
或ol
中使用。 - heading 標(biāo)簽嚴(yán)格按照升序聲明恕出,配合
section
或其它元素(例如p標(biāo)簽)正確反映界面內(nèi)容結(jié)構(gòu):
<h1>Page title</h1>
<section>
<h2>Section Heading</h2>
…
<h3>Sub-section Heading</h3>
</section>
- 使用
<meta charset="UTF-8">
指定網(wǎng)站字符集編碼询枚。 - img 元素引用的圖片資源長(zhǎng)寬比應(yīng)該和 img 當(dāng)前應(yīng)用的長(zhǎng)寬比相同,不然可能造成圖片扭曲浙巫。
- 添加
<!DOCTYPE html>
以防止瀏覽界面渲染異常金蜀。
3. 網(wǎng)站是否應(yīng)用了最佳實(shí)踐策略
> 1) 使用 target="_blank"
的 <a>
鏈接如果沒有聲明 rel="noopener noreferrer"
存在安全風(fēng)險(xiǎn)刷后。
當(dāng)頁(yè)面鏈接至使用 target="_blank" 的另一個(gè)頁(yè)面時(shí),新頁(yè)面將與舊頁(yè)面在同一個(gè)進(jìn)程上運(yùn)行渊抄。如果新頁(yè)面正在執(zhí)行開銷極大的 JavaScript尝胆,舊頁(yè)面性能可能會(huì)受影響。并且新的頁(yè)面可以通過 window.opener
訪問舊的窗口對(duì)象护桦,比如它可以使用 window.opener.location = url
將舊頁(yè)面導(dǎo)航至不同的網(wǎng)址含衔。
> 2) 檢查瀏覽器端控制臺(tái)是否有告警和錯(cuò)誤提示,通過指示定位問題并解決嘶炭。
> 3) http 和 https 協(xié)議地址不混用
瀏覽器已經(jīng)逐漸開始禁止不用協(xié)議資源的混用抱慌,比如使用 http 協(xié)議的 web 服務(wù)器加載 https 協(xié)議開頭的資源,因此可能出現(xiàn)以下幾種情況:
- 加載了混合內(nèi)容眨猎,但會(huì)出現(xiàn)警告抑进;
- 不加載混合內(nèi)容,直接會(huì)顯示空白內(nèi)容睡陪;
- 在加載混合內(nèi)容之前寺渗,會(huì)出現(xiàn)類似是否“顯示”,或存在不安全風(fēng)險(xiǎn)而被“阻止”的提示兰迫!
應(yīng)該考慮以下方式進(jìn)行優(yōu)化:
- 將站點(diǎn)加載的部分協(xié)議混用外部資源放入自己的服務(wù)器進(jìn)行托管信殊;
- 對(duì)于部署了 https 的網(wǎng)站,在網(wǎng)頁(yè)聲明
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"
將 http 請(qǐng)求轉(zhuǎn)換成 https 請(qǐng)求汁果; - 對(duì)于部署了 https 的網(wǎng)站涡拘,在服務(wù)器端添加請(qǐng)求首部:
header("Content-Security-Policy: upgrade-insecure-requests")
也可以達(dá)到一樣的效果; - 對(duì)于同時(shí)支持 http 和 https 訪問的網(wǎng)站,考慮使用相對(duì)協(xié)議据德,而不直接明文指定協(xié)議:
<script src="http://path/to/js">
鳄乏,瀏覽器發(fā)送請(qǐng)求時(shí)會(huì)根據(jù)當(dāng)前頁(yè)面協(xié)議進(jìn)行動(dòng)態(tài)切換。 - 類同于使用相對(duì)協(xié)議棘利,使用相對(duì)URL也能達(dá)到目的橱野,不過增加了資源之間的耦合度:
<script src="./path/to/js"></script>
> 4) 避免使用 AppCache
AppCache已被廢棄 考慮使用service worker的Cache API,
> 5) 避免使用 document.write()
對(duì)于網(wǎng)速較慢(2G善玫、3G或較慢的WLAN)的用戶水援,外部腳本通過document.write()動(dòng)態(tài)注入會(huì)使頁(yè)面內(nèi)容的顯示延遲數(shù)十秒。
> 6) 避免使用 mutation events
以下mutation events會(huì)損害性能茅郎,在DOM事件規(guī)范中已經(jīng)棄用:
- DOMAttrModified
- DOMAttributeNameChanged
- DOMCharacterDataModified
- DOMElementNameChanged
- DOMNodeInserted
- DOMNodeInsertedIntoDocument
- DOMNodeRemoved
- DOMNodeRemovedFromDocument
- DOMSubtreeModified
建議使用 MutationObserver 替代
> 7) 避免使用 Web SQL
建議替換為IndexedDB
> 8) 避免加載過于龐大的 DOM 樹
大型的DOM樹會(huì)以多種方式降低頁(yè)面性能:
- 網(wǎng)絡(luò)效率和負(fù)載性能蜗元,如果你的服務(wù)器發(fā)送一個(gè)大的DOM樹,你可能會(huì)運(yùn)送大量不必要的字節(jié)系冗。這也可能會(huì)減慢頁(yè)面加載時(shí)間奕扣,因?yàn)闉g覽器可能會(huì)解析許多沒有顯示在屏幕上的節(jié)點(diǎn)。
- 運(yùn)行時(shí)性能毕谴。當(dāng)用戶和腳本與頁(yè)面交互時(shí)成畦,瀏覽器必須不斷重新計(jì)算節(jié)點(diǎn)的位置和樣式。一個(gè)大的DOM樹與復(fù)雜的樣式規(guī)則相結(jié)合可能會(huì)嚴(yán)重減慢渲染速度涝开。
- 內(nèi)存性能循帐。如果使用通用查詢選擇器(例如,document.querySelectorAll('li') 您可能會(huì)無(wú)意中將引用存儲(chǔ)到大量的節(jié)點(diǎn))舀武,這可能會(huì)壓倒用戶設(shè)備的內(nèi)存功能拄养。
一個(gè)最佳的DOM樹:
- 總共少于1500個(gè)節(jié)點(diǎn)。
- 最大深度為32個(gè)節(jié)點(diǎn)银舱。
- 沒有超過60個(gè)子節(jié)點(diǎn)的父節(jié)點(diǎn)瘪匿。
- 一般來(lái)說,只需要在需要時(shí)尋找創(chuàng)建DOM節(jié)點(diǎn)的方法寻馏,并在不再需要時(shí)將其銷毀棋弥。
> 9) 允許用戶粘貼密碼
密碼粘貼提高了安全性,因?yàn)樗褂脩裟軌蚴褂妹艽a管理器诚欠。密碼管理員通常為用戶生成強(qiáng)密碼顽染,安全地存儲(chǔ)密碼,然后在用戶需要登錄時(shí)自動(dòng)將其粘貼到密碼字段中轰绵。
刪除阻止用戶粘貼到密碼字段的代碼粉寞。使用事件斷點(diǎn)中的Clipboard paste來(lái)打斷點(diǎn),可以快速找到阻止粘貼密碼的代碼左腔。比如下列這種阻止粘貼密碼的代碼:
var input = document.querySelector('input');
input.addEventListener('paste', (e) => {
e.preventDefault();
});
4. 網(wǎng)站搜索引擎優(yōu)化SEO
- 添加視口聲明
<meta name="viewport">
并指定 with 和 device-width 來(lái)優(yōu)化移動(dòng)端顯示唧垦。 - document 添加
title
屬性以讓讀屏器和搜索引擎正確識(shí)別網(wǎng)站內(nèi)容。 - 添加
meta
desctiption 標(biāo)簽來(lái)描述網(wǎng)站內(nèi)容<meta name="description" content="Put your description here.">
液样。 - 為鏈接標(biāo)簽添加描述文本
<a href="videos.html">basketball videos</a>
振亮,以清楚傳達(dá)這個(gè)超鏈接的指向內(nèi)容。 - 使用正確的 href 鏈接地址以讓搜索引擎正確跟蹤實(shí)際網(wǎng)址蓄愁,以下是反例:
<a onclick="goto('https://example.com')">
- 不要使用 meta 標(biāo)簽來(lái)禁用搜索引擎爬蟲爬取你的網(wǎng)頁(yè)双炕,以下是反例:
<meta name="robots" content="noindex"/>
,相對(duì)的可以特殊的排除一些爬蟲爬却樽ァ:<meta name="AdsBot-Google" content="noindex"/>
妇斤。 - image 圖片元素中使用具有明確意圖和含義的 alt 屬性文字來(lái)說明圖片信息,并且避免使用一些非特定指代詞比如:
"chart", "image", "diagram"
(圖表丹拯、圖片)站超。 - 不要使用插件來(lái)顯示您的內(nèi)容,即避免使用
embed
乖酬、object
死相、applet
等標(biāo)簽來(lái)引入資源。 - 正確放置
robots.txt
到網(wǎng)站根目錄下咬像,它描述了此網(wǎng)站中的哪些內(nèi)容是不應(yīng)被搜索引擎的獲取的算撮,哪些是可以被獲取的生宛。通常一些后臺(tái)文件(如css、js)和用戶隱私的文件不用被搜索引擎抓取肮柜,另外有些文件頻繁被蜘蛛抓取陷舅,但是這些文件又是不重要的,那么可以用robots.txt進(jìn)行屏蔽审洞。
一個(gè)實(shí)例:
User-agent: *
Allow: /
Disallow: /wp-admin/
Disallow: /wp-includes/
Disallow: /wp-content/plugins/
Disallow: /?r=*
- 向搜索引擎提交網(wǎng)站地圖 sitemap.xml莱睁,xml格式的文本就是專門拿來(lái)給電腦進(jìn)行閱讀的語(yǔ),而 sitemap.xml就是搜尋引擎利用這個(gè)規(guī)范芒澜,讓網(wǎng)站主可以使用它來(lái)制作一個(gè)包含了網(wǎng)站內(nèi)所有網(wǎng)頁(yè)的目錄檔案仰剿,提供給搜尋引擎的爬蟲閱讀。就像是一個(gè)地圖一樣痴晦,讓搜尋引擎可以知道網(wǎng)站內(nèi)到底有些什么網(wǎng)頁(yè)南吮。
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://nojsja.github.io/blogs/2019/10/26/63567fa4.html/</loc>
<lastmod>2021-04-29T10:02:04.853Z</lastmod>
</url>
<url>
<loc>https://nojsja.github.io/blogs/2020/03/26/7507699.html/</loc>
<lastmod>2021-04-29T10:01:30.661Z</lastmod>
</url>
</urlset>
性能測(cè)試工具 lighthouse
以上提到的性能監(jiān)測(cè)指標(biāo)通過 lighthouse 網(wǎng)站性能監(jiān)測(cè)工具可以自動(dòng)化分析和生成性能測(cè)試結(jié)果數(shù)據(jù),較新版本的 chrome
和 采用 chromium
架構(gòu)的 edge
瀏覽器都已經(jīng)自帶了誊酌,較低版本的 chrome
瀏覽器可以通過在商店搜索插件安裝旨袒。安裝后使用 F12 打開控制臺(tái),切換到 lighthouse
一欄即可直接使用了:
如圖术辐,可以自行決定勾選測(cè)試項(xiàng)目和針對(duì)測(cè)試平臺(tái)(桌面/移動(dòng)端)砚尽,最后點(diǎn)擊生成報(bào)告即可開始運(yùn)行自動(dòng)化測(cè)試任務(wù),并在測(cè)試完成后打開一個(gè)分析結(jié)果頁(yè)面:
結(jié)果界面:
至此我們就能對(duì)網(wǎng)站的整體性能進(jìn)行一些評(píng)估了辉词,上圖中的Performance
必孤、Accessibility
、Best Practices
瑞躺、和 SEO
依次對(duì)應(yīng)上文我們提及的網(wǎng)站整體性能敷搪、網(wǎng)站可訪問性、網(wǎng)站最佳實(shí)踐幢哨、搜索引擎SEO優(yōu)化等指標(biāo)赡勘。我們可以點(diǎn)擊每一個(gè)具體項(xiàng)目來(lái)查看測(cè)試工具給出的優(yōu)化建議:
測(cè)試結(jié)果很大程度上取決于你的 http 靜態(tài)資源服務(wù)器的資源加載速度,比如在不使用代理的情況下捞镰,使用 github pages 服務(wù)來(lái)托管靜態(tài)資源會(huì)比使用國(guó)內(nèi)的 gitee pages 托管服務(wù)稍慢闸与,而且可能出現(xiàn)部分資源加載失敗的問題。因此國(guó)內(nèi)用戶可以使用 gitee pages 來(lái)替代 github pages岸售,不過 gitee 非付費(fèi)用戶沒有代碼自動(dòng)構(gòu)建部署功能践樱,需要使用下文提到的 github action
來(lái)進(jìn)行自動(dòng)化登錄并觸發(fā)構(gòu)建和部署。
注意: 某些瀏覽器上安裝的插件會(huì)影響測(cè)試結(jié)果凸丸,因?yàn)椴寮赡軙?huì)發(fā)起請(qǐng)求加載其它腳本啥的拷邢。這種情況下可以直接使用 npm 包管理器全局安裝 npm install -g lighthouse
,然后使用命令行啟動(dòng)測(cè)試流程:lighthouse https://nojsja.gitee.io/blogs --view --preset=desktop --output-path='/home/nojsja/Desktop/lighthouse.html'
屎慢。最終會(huì)根據(jù)指定的地址生成一個(gè)可直接瀏覽器測(cè)試結(jié)果網(wǎng)頁(yè)文件 lighthouse.html
瞭稼,可以直接打開進(jìn)行性能排查忽洛。
基于 hexo 框架的網(wǎng)站優(yōu)化
之前寫的一篇文章 《前端性能優(yōu)化技巧詳解(1)》,詳細(xì)的描述了前端性能優(yōu)化的一些方面环肘,這篇文章不會(huì)再羅列每一點(diǎn)脐瑰,只會(huì)對(duì)博客中實(shí)際應(yīng)用的優(yōu)化手段進(jìn)行說明,大致被劃分成這幾個(gè)方面:
- 優(yōu)化資源加載時(shí)間
- 優(yōu)化界面運(yùn)行性能
- 網(wǎng)站最佳實(shí)踐
- 網(wǎng)站 SEO 優(yōu)化
1. 優(yōu)化資源加載時(shí)間
常見的加載時(shí)間優(yōu)化方式包含以下:
- 提高網(wǎng)頁(yè)資源并行加載能力
- 延遲加載不必要的外部資源
- 減少碎片化的外部資源請(qǐng)求,小文件做合并
- 增加網(wǎng)站帶寬
? 使用 defer/async 異步下載 script 腳本資源
HTML在解析時(shí)遇到聲明的<script>
腳本會(huì)立即下載和執(zhí)行荠商,往往會(huì)延遲界面剩余部分的解析莱没,造成界面白屏的情況酷鸦。比較古老的優(yōu)化方式之一就是將腳本放到HTML文檔末尾饰躲,這樣子解決了白屏的問題,可是在DOM文檔結(jié)構(gòu)復(fù)雜冗長(zhǎng)時(shí)臼隔,也會(huì)造成一定的界面腳本下載和執(zhí)行延遲嘹裂,script標(biāo)簽新屬性async和defer可以解決此類問題:
- defer腳本:聲明 defer 屬性的外部
<script>
腳本下載時(shí)不會(huì)阻塞HTML的解析和渲染,并且會(huì)在HTML渲染完成并且可實(shí)際操作之后開始執(zhí)行(DOMContentLoaded
事件被觸發(fā)之前)摔握,各個(gè)腳本解析執(zhí)行順序?qū)?yīng)聲明時(shí)的位置順序寄狼,執(zhí)行完成后會(huì)觸發(fā)頁(yè)面DOMContentLoaded
事件。 - async腳本:聲明 async 屬性的外部
<script>
腳本下載時(shí)不會(huì)阻塞HTML的解析和渲染氨淌,各個(gè)腳本的下載和執(zhí)行完全獨(dú)立泊愧,下載完成后即開始執(zhí)行,所以執(zhí)行順序不固定盛正,與DOMContentLoaded事件的觸發(fā)沒有關(guān)聯(lián)性删咱。
在我的博客網(wǎng)站中有使用 Bootstrap
外部依賴的樣式和js腳本,但是需要確保他們的聲明順序在靠前的位置豪筝,因?yàn)槭褂卯惒郊夹g(shù)之后痰滋,內(nèi)聯(lián)同步執(zhí)行的其它<script>
腳本的執(zhí)行順序就不能保證了,因此不能使用 defer/async
屬性進(jìn)行優(yōu)化续崖。
通常 async/defer
會(huì)用于優(yōu)化一些獨(dú)立的子組件依賴腳本的加載袜刷,比如用于博客文章中的導(dǎo)航條跳轉(zhuǎn)的腳本墩蔓,它的執(zhí)行順序完全不收到其它部分的制約昏名,因此可以獨(dú)立出來(lái)使用 async
屬性進(jìn)行優(yōu)化样刷。但是需要確保該腳本作用的 導(dǎo)航條
DOM元素聲明位于腳本引入位置之前镇饮,以防止出現(xiàn)腳本執(zhí)行時(shí) DOM 元素還未渲染的狀態(tài),引起腳本錯(cuò)誤。
? 使用 async 函數(shù)異步加載外部資源
以下 async 函數(shù)作用就是根據(jù)傳入的 url 創(chuàng)建 link/script
標(biāo)簽并添加到 <head>
標(biāo)簽中以動(dòng)態(tài)加載外部資源肺缕,并且在存在回調(diào)函數(shù)時(shí)監(jiān)聽資源加載情況,加載完畢后再執(zhí)行回調(diào)函數(shù)彤路。值得注意的是本方法與直接通過script
聲明依賴資源的不同之處在于不會(huì)阻塞界面,腳本的下載和執(zhí)行都是異步的。
博客中常用于在某個(gè)特殊的情況下利用編程方法動(dòng)態(tài)載入外部依賴庫(kù)丽涩,并在回調(diào)函數(shù)內(nèi)進(jìn)行外部庫(kù)的初始化惑惶。例如我的博客使用了一個(gè)音樂播放器組件打瘪,在網(wǎng)頁(yè)可視區(qū)域滾動(dòng)到包含這個(gè)組件的尚未初始化的 DOM 元素時(shí)崔泵,就是用 async
來(lái)請(qǐng)求服務(wù)器的 js 腳本資源,加載完成后在回調(diào)函數(shù)里初始化這個(gè)音樂播放器。
/**
* async [異步腳本加載]
* @author nojsja
* @param {String} u [資源地址]
* @param {Function} c [回調(diào)函數(shù)]
* @param {String} tag [加載資源類型 link | script]
* @param {Boolean} async [加載 script 資源時(shí)是否需要聲明 async 屬性]
*/
function async(u, c, tag, async) {
var head = document.head ||
document.getElementsByTagName('head')[0] ||
document.documentElement;
var d = document, t = tag || 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
async = ['async', 'defer'].includes(async) ? async : !!async;
switch(t) {
case 'script':
o.src = u;
if (async) o[async] = true;
break;
case 'link':
o.type = "text/css";
o.href = u;
o.rel = "stylesheet";
break;
default:
o.src = u;
break;
}
/* callback */
if (c) {
if (o.readyState) {//IE
o.onreadystatechange = function (e) {
if (o.readyState == "loaded"
|| o.readyState == "complete") {
o.onreadystatechange = null;
c(null, e)();
}
};
} else {//其他瀏覽器
o.onload = function (e) {
c(null, e);
};
}
}
s.parentNode.insertBefore(o, head.firstChild);
}
? 使用瀏覽器 onfocus 事件延遲外部資源加載
通過用戶和界面交互觸發(fā)一些事件后,滿足了外部資源加載的條件,再觸發(fā)外部資源的加載塞弊,也屬于延遲加載的一種優(yōu)化。
例如我的博客中右側(cè)導(dǎo)航條區(qū)域有個(gè)搜索框可以搜索博客文章诀黍,本身這個(gè)搜索是通過查找本地預(yù)先生成的一個(gè)資源索引靜態(tài)XML文件來(lái)實(shí)現(xiàn)的。文章和內(nèi)容較多這個(gè)這個(gè)XML文件就會(huì)變得龐大,如果在網(wǎng)頁(yè)首次加載時(shí)就下載它一定會(huì)造成網(wǎng)頁(yè)帶寬和網(wǎng)絡(luò)請(qǐng)求數(shù)量的占用郁轻。因此考慮在用戶點(diǎn)擊搜索框?qū)⒔裹c(diǎn)集中到上面時(shí)再觸發(fā)XML的異步下載故觅,同時(shí)為了不影響使用體驗(yàn),可以在加載過程中設(shè)置 loading 效果以指示用戶延遲操作。
? 使用 preload/prefetch/preconnect 進(jìn)行預(yù)加載優(yōu)化
- preload 用來(lái)預(yù)加載當(dāng)前頁(yè)面所需的資源它浅,如圖像,CSS镊折,JavaScript 和字體文件,它的加載優(yōu)先級(jí)比 prefetch 更高,同時(shí)也要注意 preload 并不會(huì)阻塞 window 的 onload 事件升熊。博客中有使用它來(lái)預(yù)加載 css 中引用的字體:
<link href="/blogs/fonts/fontawesome-webfont.woff2?v=4.3.0" rel=preload as="font">
,針對(duì)不同的資源類型需要添加不同的as
標(biāo)記信息,如果是跨域加載的話注意添加crossorigin
屬性聲明渊抽。 - prefetch 是一個(gè)低優(yōu)先級(jí)的資源提示,一旦一個(gè)頁(yè)面加載完畢就會(huì)開始下載其他的預(yù)加載資源帮辟,并且將他們存儲(chǔ)在瀏覽器的緩存中。其中 prefretch 又包含:
link蔓榄、DNS 和 prerendering
三中類型的預(yù)加載請(qǐng)求。link prefetch 比如:<link rel="prefetch" href="/path/to/pic.png">
允許瀏覽器獲取資源并將他們存儲(chǔ)在緩存中;DNS prefetch 允許瀏覽器在用戶瀏覽頁(yè)面時(shí)在后臺(tái)運(yùn)行 DNS prerender 和 prefetch 非常相似勉躺,它們都優(yōu)化了下一頁(yè)資源的未來(lái)請(qǐng)求柳弄,區(qū)別是 prerender 在后臺(tái)渲染了整個(gè)頁(yè)面嚣伐,因此應(yīng)該小心使用,可能會(huì)造成網(wǎng)絡(luò)帶寬的浪費(fèi)基茵。 - preconnect 允許瀏覽器在一個(gè) HTTP 請(qǐng)求正式發(fā)給服務(wù)器前預(yù)先執(zhí)行一些操作,這包括 DNS 解析,TLS 協(xié)商烙肺,TCP 握手氏堤,這消除了往返延遲并為用戶節(jié)省了時(shí)間。比如博客中:不算子統(tǒng)計(jì)庫(kù)的網(wǎng)頁(yè)預(yù)連接
<link rel="preconnect" crossorigin>
脚祟。
? 使用 hexo 插件壓縮代碼文件和圖片文件
壓縮靜態(tài)資源也是一種節(jié)省網(wǎng)絡(luò)帶寬,提高請(qǐng)求響應(yīng)速度的方式。通常采用工程化的方式進(jìn)行配置娃循,而不用手動(dòng)對(duì)每張圖片進(jìn)行壓縮。我的博客中使用了 hexo 的一款壓縮插件 Hexo-all-minifier捞蚂,通過壓縮 HTML、CSS、JS 和圖片來(lái)優(yōu)化博客訪問速度解寝。
安裝:
npm install hexo-all-minifier --save
在 config.yml
配置文件中啟用:
# ---- 代碼和資源壓縮
html_minifier:
enable: true
exclude:
css_minifier:
enable: true
exclude:
- '*.min.css'
js_minifier:
enable: true
mangle: true
compress: true
exclude:
- '*.min.js'
image_minifier:
enable: true
interlaced: false
multipass: false
optimizationLevel: 2 # 壓縮等級(jí)
pngquant: false
progressive: true # 是否啟用漸進(jìn)式圖片壓縮
資源的壓縮比較消耗性能和時(shí)間与学,可以考慮在開發(fā)環(huán)境下不啟用這些插件以加快開發(fā)環(huán)境啟動(dòng)抑片。比如單獨(dú)指定一個(gè) _config.dev.yml
然后把上述插件全部關(guān)閉即可截汪,參考 package.json
中的腳本字段聲明:
{
...
"scripts": {
"prestart": "hexo clean --config config.dev.yml; hexo generate --config config.dev.yml",
"prestart:prod": "hexo clean; hexo generate",
"predeploy": "hexo clean; hexo generate",
"start": "hexo generate --config config.dev.yml; hexo server --config config.dev.yml",
"start:prod": "hexo generate --config config.dev.yml; hexo server",
"performance:prod": "lighthouse https://nojsja.gitee.io/blogs --view --preset=desktop --output-path='/home/nojsja/Desktop/lighthouse.html'",
"performance": "lighthouse http://localhost:4000/blogs --view --preset=desktop --output-path='/home/nojsja/Desktop/lighthouse.html'",
"deploy": "hexo deploy"
}
}
? 編寫 hexo-img-lazyload 插件增加圖片懶加載特性
在進(jìn)行博客優(yōu)化的時(shí)候?yàn)榱藢W(xué)習(xí) hexo 自己的插件系統(tǒng)焰枢,使用 IntersectionObserver API
來(lái)編寫了一個(gè)圖片懶加載插件:hexo-img-lazyload暑椰,可以通過 npm 命令進(jìn)行安裝:npm i hexo-img-lazyload
。
效果預(yù)覽:
插件主要原理就是監(jiān)聽博客構(gòu)建流程的鉤子事件召夹,拿到構(gòu)建好的代碼字符串昏兆,然后代碼中原生的圖片聲明比如:<img src="path/to/xx.jpg">
通過正則全局匹配并替換為:<img src="path/to/loading" data-src="path/to/xx.jpg">
。
function lazyProcessor(content, replacement) {
return content.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, function (str, p1, p2, p3) {
if (/data-loaded/gi.test(str)) {
return str;
}
if (/no-lazy/gi.test(str)) {
return str;
}
return `<img ${p1} src="${emptyStr}" lazyload data-loading="${replacement}" data-src="${p2}" ${p3}>`;
});
}
替換之后還需要使用 hexo 的代碼注入功能將我們自己編寫的代碼注入到每個(gè)構(gòu)建好的界面中。
hexo 代碼注入:
/* registry scroll listener */
hexo.extend.injector.register('body_end', function() {
const script = `
<script>
${observerStr}
</script>`;
return script;
}, injectionType)
被注入的用于監(jiān)聽待加載圖片元素是否進(jìn)入可視區(qū)域以進(jìn)行動(dòng)態(tài)加載的部分代碼曲梗,使用了 IntersectionObserver API
而不是 window.onscroll
事件的方式,前者具有更好的性能笤虫,由瀏覽器統(tǒng)一監(jiān)聽所有元素位置信息更改并分發(fā)滾動(dòng)事件結(jié)果:
(function() {
/* avoid garbage collection */
window.hexoLoadingImages = window.hexoLoadingImages || {};
function query(selector) {
return document.querySelectorAll(selector);
}
/* registry listener */
if (window.IntersectionObserver) {
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
// in view port
if (entry.isIntersecting) {
observer.unobserve(entry.target);
// proxy image
var img = new Image();
var imgId = "_img_" + Math.random();
window.hexoLoadingImages[imgId] = img;
img.onload = function() {
entry.target.src = entry.target.getAttribute('data-src');
window.hexoLoadingImages[imgId] = null;
};
img.onerror = function() {
window.hexoLoadingImages[imgId] = null;
}
entry.target.src = entry.target.getAttribute('data-loading');
img.src = entry.target.getAttribute('data-src');
}
});
});
query('img[lazyload]').forEach(function (item) {
observer.observe(item);
});
} else {
/* fallback */
query('img[lazyload]').forEach(function (img) {
img.src = img.getAttribute('data-src');
});
}
}).bind(window)();
? 使用 IntersectionObserver API 懶加載其它資源
IntersectionObserver API
由于性能更好已經(jīng)在我的博客中作為一種主要的資源懶加載方式,我還使用它來(lái)優(yōu)化博客評(píng)論組件 Valine 的加載罚拟。一般評(píng)論組件都位于博客文章的最下方,因此剛載入文章頁(yè)面時(shí)完全沒有必要進(jìn)行評(píng)論組件的資源加載,可以考慮使用 IntersectionObserver
監(jiān)聽評(píng)論組件是否進(jìn)入視口叔扼,進(jìn)入后再使用 async
異步腳本下載并回調(diào)執(zhí)行評(píng)論系統(tǒng)初始化。
另一方面每篇文章底部的音樂播放器 Aplayer 也使用了類似的加載策略,可以說優(yōu)化效果屢試不爽价捧!
? 使用 CDN 加載外部依賴腳本
CDN 即內(nèi)容分發(fā)網(wǎng)絡(luò)。CDN 服務(wù)商將靜態(tài)資源緩存到遍布全國(guó)的高性能加速節(jié)點(diǎn)上推正,當(dāng)用戶訪問相應(yīng)的業(yè)務(wù)資源時(shí)掌测,CDN系統(tǒng)能夠?qū)崟r(shí)地根據(jù)網(wǎng)絡(luò)流量和各節(jié)點(diǎn)的連接負(fù)載狀況、到用戶的距離和響應(yīng)時(shí)間 等綜合信息將用戶的請(qǐng)求重新導(dǎo)向離用戶最近的服務(wù)節(jié)點(diǎn)上竞端,使內(nèi)容能夠傳輸?shù)母欤臃€(wěn)定乘陪⊥程ǎ可以提升首次請(qǐng)求的響應(yīng)能力。
博客中一些公用外部庫(kù)比如 Bootstrap
和 jQuery
都是使用的外部 CDN 資源地址啡邑,一方面可以減小當(dāng)前主站的網(wǎng)頁(yè)帶寬消耗贱勃,另一方面 CDN 也能提供一些資源下載加速。
? 使用 Aplayer 替代 iframe 加載網(wǎng)易云音樂
博客之前的版本會(huì)在文章界面的底部嵌入一個(gè)網(wǎng)易云音樂自己的播放器,這個(gè)播放器其實(shí)是一個(gè) iframe 像這樣:
<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="http://music.163.com/outchain/player?type=2&id=781246&auto=1&height=66"></iframe>
iframe 加載的時(shí)候會(huì)加載一堆東西舞丛,雖然可以通過 lazy
屬性來(lái)進(jìn)行懶加載怀骤,不過iframe 也有很多缺點(diǎn):
- iframe會(huì)阻塞主頁(yè)面的onload事件
- iframe和主頁(yè)面共享HTTP連接池末患,而瀏覽器對(duì)相同域的連接有限制哗讥,所以會(huì)影響頁(yè)面的并行加載
- iframe不利于網(wǎng)頁(yè)布局
- iframe對(duì)移動(dòng)端不友好
- iframe的反復(fù)重新加載可能導(dǎo)致一些瀏覽器的內(nèi)存泄露
- iframe中的數(shù)據(jù)傳輸復(fù)雜
- iframe不利于SEO
新版本將 iframe 播放器換成了 Aplayer
并且把自己喜歡的一個(gè)歌曲列表上傳到了另一個(gè) gitee pages 倉(cāng)庫(kù) 進(jìn)行靜態(tài)托管瑞驱,可以通過以下方式在博客底部加載一個(gè)自定義歌曲列表:
var ap = new APlayer({
container: document.getElementById('aplayer'),
theme: '#e9e9e9',
audio: [{
name: '存在信號(hào)',
artist: 'AcuticNotes',
url: 'https://nojsja.gitee.io/static-resources/audio/life-signal.mp3',
cover: 'https://nojsja.gitee.io/static-resources/audio/life-signal.jpg'
},
{
name: '遺サレタ場(chǎng)所/斜光',
artist: '岡部啓一',
url: 'https://nojsja.gitee.io/static-resources/audio/%E6%96%9C%E5%85%89.mp3',
cover: 'https://nojsja.gitee.io/static-resources/audio/%E6%96%9C%E5%85%89.jpg'
}]
});
預(yù)覽圖:
2. 優(yōu)化界面運(yùn)行性能
? 優(yōu)化頁(yè)面的回流和重繪情況
1)概念
回流(重排闻书,reflow)和重繪(repaint)是瀏覽器渲染網(wǎng)頁(yè)必不可少的一個(gè)過程,回流主要HTML渲染過程中元素空間位置和大小改變引起的 DOM 樹重新渲染池摧,而重繪是由于節(jié)點(diǎn)的樣式屬性發(fā)生改變,并不會(huì)影響空間布局棘幸。從性能而言回流的性能消耗大,而且容易產(chǎn)生級(jí)聯(lián)效應(yīng)菇晃,即一個(gè)正常 DOM 樹的流布局中荒辕,一個(gè)元素發(fā)生回流后卓囚,該元素位置之后的元素全部都會(huì)發(fā)生回流并重新渲染飞袋,重繪相對(duì)性能消耗更小沸版。
2)怎樣有效判斷界面的回流和重繪情況卧晓?
其實(shí)一般基于 chromium 架構(gòu)瀏覽器都附帶一個(gè)網(wǎng)頁(yè)開發(fā)工具 Devtools抖锥,但可以說絕大多數(shù)前端開發(fā)者都沒有認(rèn)真了解過這個(gè)工具的具體用途屎飘,只是把它用作簡(jiǎn)單的 log調(diào)試掰烟、網(wǎng)頁(yè)請(qǐng)求追蹤和樣式調(diào)試這些基礎(chǔ)功能∫诒猓回流和重繪也是可以通過它來(lái)進(jìn)行可視化度量的:F12打開 Devtools,找到右上角三個(gè)點(diǎn)的折疊按鈕依次打開 -> More Tools(更多工具) -> Rendering (渲染) - 勾選前兩項(xiàng) Paint Flashing
(高亮重繪區(qū)域) 和 Layout Shift Regions
(高亮回流區(qū)域),現(xiàn)在重新回到你打開的頁(yè)面進(jìn)行操作霹琼,操作過程中發(fā)生了回流的區(qū)域會(huì)變成藍(lán)色胃夏,發(fā)生了重繪的區(qū)域會(huì)變成綠色蚕愤,持續(xù)時(shí)間不長(zhǎng)答恶,注意觀察。
Repaint:
Reflow:
除了用于可視化界面回流/重繪的情況萍诱,Devtools 還有其他一些很實(shí)用的功能悬嗓,比如:Coverage Tools
可以用于分析界面上引入的外部 css/js
腳本內(nèi)容的使用覆蓋率,就是說我們能通過這個(gè)工具衡量引入的外部文件的使用情況裕坊,使用頻次較低的外部資源可以考慮內(nèi)聯(lián)或是直接手寫實(shí)現(xiàn)包竹,提升引入外部資源的性價(jià)比
。
? 使用節(jié)流和去抖思想優(yōu)化滾動(dòng)事件監(jiān)聽
在面對(duì)一些需要進(jìn)行調(diào)用控制的函數(shù)高頻觸發(fā)場(chǎng)景時(shí)籍凝,可能有人會(huì)對(duì)何時(shí)使用節(jié)流何時(shí)使用去抖產(chǎn)生疑問周瞎。這里通過一個(gè)特性進(jìn)行簡(jiǎn)單區(qū)分:如果你需要保留短時(shí)間內(nèi)高頻觸發(fā)的最后一次結(jié)果時(shí),那么使用去抖函數(shù)饵蒂,如果你需要對(duì)函數(shù)的調(diào)用次數(shù)進(jìn)行限制声诸,以最佳的調(diào)用間隔時(shí)間保持函數(shù)的持續(xù)調(diào)用而不關(guān)心是否是最后一次調(diào)用結(jié)果時(shí),請(qǐng)使用節(jié)流函數(shù)退盯。
比如 echarts 圖常常需要在窗口 resize 之后重新使用數(shù)據(jù)渲染彼乌,但是直接監(jiān)聽 resize 事件可能導(dǎo)致短時(shí)間內(nèi)渲染函數(shù)被觸發(fā)多次泻肯。我們可以使用函數(shù)去抖的思想,監(jiān)聽 resize 事件后在監(jiān)聽器函數(shù)里獲取參數(shù)再使用參數(shù)調(diào)用事先初始化好了的 throttle 函數(shù)慰照,保證 resize 過程結(jié)束后能觸發(fā)一次實(shí)際的 echarts 重渲染即可软免。
這里給出節(jié)流函數(shù)和去抖函數(shù)的簡(jiǎn)單實(shí)現(xiàn):
/**
* fnDebounce [去抖函數(shù)]
* @author nojsja
* @param {Function} fn [需要被包裹的原始函數(shù)邏輯]
* @param {Numberl} timeout [延遲時(shí)間]
* @return {Function} [高階函數(shù)]
*/
var fnDebounce = function(fn, timeout) {
var time = null;
return function() {
if (!time) return time = Date.now();
if (Date.now() - time >= timeout) {
time = null;
return fn.apply(this, [].slice.call(arguments));
} else {
time = Date.now();
}
};
};
/**
* fnThrottle [節(jié)流函數(shù)]
* @author nojsja
* @param {Function} fn [需要被包裹的原始函數(shù)邏輯]
* @param {Numberl} timeout [延遲時(shí)間]
* @return {Function} [高階函數(shù)]
*/
var fnThrottle = function(fn, timeout) {
var time = null;
return function() {
if (!time) return time = Date.now();
if ((Date.now() - time) >= timeout) {
time = null;
return fn.apply(this, [].slice.call(arguments));
}
};
};
博客中文章右側(cè)的內(nèi)容導(dǎo)航欄會(huì)根據(jù)滾動(dòng)條位置自動(dòng)切換 fixed
布局和一般流布局,這么做是為了讓導(dǎo)航欄在閱讀文章過程中也能正常呈現(xiàn)焚挠,不會(huì)被隱藏到頂部:
/* 限150ms才能觸發(fā)一次滾動(dòng)檢測(cè) */
(window).on('scroll', fnThrottle(function() {
var rectbase = $$tocBar.getBoundingClientRect();
if (rectbase.top <= 0) {
$toc.css('left', left);
(!$toc.hasClass('toc-fixed')) && $toc.addClass('toc-fixed');
$toc.hasClass('toc-normal') && $toc.removeClass('toc-normal');
} else {
$toc.css('left', '');
$toc.hasClass('toc-fixed') && $toc.removeClass('toc-fixed');
(!$toc.hasClass('toc-normal')) && $toc.addClass('toc-normal');
($$toc.scrollTop > 0) && ($$toc.scrollTop = 0);
}
}, 150));
? IntersectionObserver API 的 polyfill 兼容策略
文章中提到 IntersectionObserver API
已經(jīng)用于博客中各個(gè)界面組件的懶加載功能膏萧,它的性能更好、功能也更加全面蝌衔。但是網(wǎng)頁(yè)開發(fā)中我們通常會(huì)考慮各個(gè) API 的兼容性榛泛,這里可以通過 Can I Use 這個(gè)兼容性報(bào)告網(wǎng)站進(jìn)行查看,從下圖可知這個(gè) API 的兼容情況還是可以的噩斟,桌面端很多較高版本瀏覽器均已支持:
因此為了解決個(gè)別低版本瀏覽器的兼容性問題曹锨,這里采用了一種比較極端的處理方式。常規(guī)情況下我們需要引入外部 [xxx].polyfill.js
(xxx為相應(yīng)的 API) 來(lái)為低版本瀏覽器添加相應(yīng)功能剃允,但是對(duì)于高版本瀏覽器自身已經(jīng)支持了這個(gè) API,卻要重復(fù)下載 polyfill 庫(kù)斥废,造成網(wǎng)頁(yè)請(qǐng)求數(shù)和帶寬資源的浪費(fèi)牡肉。因此我這里不采用這種方式捧灰,因?yàn)檫@個(gè) API 大部分瀏覽器已經(jīng)支持,我們默認(rèn)不使用 <script>
標(biāo)簽引入 polyfill.js 而是通過腳本判斷當(dāng)前瀏覽器是否支持此 API统锤,如果不支持的話再使用同步XHR請(qǐng)求
遠(yuǎn)程 下載polyfill 文件毛俏,下載后使用 eval(...)
的方式執(zhí)行整個(gè)腳本。使用同步方式會(huì)阻塞當(dāng)前 js 執(zhí)行線程饲窿,請(qǐng)謹(jǐn)慎使用煌寇,此處是為了保證 IntersectionObserver
以高優(yōu)先級(jí)方式被注入到網(wǎng)頁(yè)中,不然可能引發(fā)一些使用了此 API 腳本錯(cuò)誤逾雄。
<!-- 此腳本被放置在靠近頁(yè)面首部某個(gè)位置 -->
<script>
if ('IntersectionObserver' in window &&
'IntersectionObserverEntry' in window &&
'intersectionRatio' in window.IntersectionObserverEntry.prototype) {
if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) {
Object.defineProperty(window.IntersectionObserverEntry.prototype,
'isIntersecting', {
get: function () {
return this.intersectionRatio > 0;
}
});
}
} else {
/* load polyfill sync */
sendXMLHttpRequest({
url: '/blogs/js/intersection-observer.js',
async: false,
method: 'get',
callback: function(txt) {
eval(txt);
}
});
}
</script>
? 使用 IntersectionObserver 替代原生 onscroll 事件監(jiān)聽
IntersectionObserver
通常用于界面中的一些相交檢測(cè)場(chǎng)景:
- 圖片懶加載 – 當(dāng)圖片滾動(dòng)到可見時(shí)才進(jìn)行加載
- 內(nèi)容無(wú)限滾動(dòng) – 用戶滾動(dòng)到接近滾動(dòng)容器底部時(shí)直接加載更多數(shù)據(jù)阀溶,而無(wú)需用戶操作翻頁(yè),給用戶一種網(wǎng)頁(yè)可以無(wú)限滾動(dòng)的錯(cuò)覺
- 檢測(cè)廣告的曝光情況——為了計(jì)算廣告收益嘲驾,需要知道廣告元素的曝光情況
- 在用戶看見某個(gè)區(qū)域時(shí)執(zhí)行任務(wù)淌哟、播放視頻
以內(nèi)容無(wú)限滾動(dòng)為例,古老的相交檢測(cè)方案就是使用 scroll 事件監(jiān)聽滾動(dòng)容器辽故,在監(jiān)聽器函數(shù)中獲取滾動(dòng)元素的幾何屬性判斷元素是否已經(jīng)滾動(dòng)到底部徒仓。我們知道scrollTop等屬性的獲取和設(shè)置都會(huì)導(dǎo)致頁(yè)面回流,并且如果界面需要綁定多個(gè)監(jiān)聽函數(shù)到scroll事件進(jìn)行類似操作的時(shí)候誊垢,頁(yè)面性能會(huì)大打折扣:
/* 滾動(dòng)監(jiān)聽 */
onScroll = () => {
const {
scrollTop, scrollHeight, clientHeight
} = document.querySelector('#target');
/* 已經(jīng)滾動(dòng)到底部 */
// scrollTop(向上滾動(dòng)的高度)掉弛;clientHeight(容器可視總高度)症见;scrollHeight(容器的總內(nèi)容長(zhǎng)度)
if (scrollTop + clientHeight === scrollHeight) { /* do something ... */ }
}
這里以一個(gè)簡(jiǎn)單實(shí)現(xiàn)的圖片懶加載功能來(lái)介紹下它的使用方式,詳細(xì)使用可以查看博客:《前端性能優(yōu)化技巧詳解(1)》殃饿。
(function lazyload() {
var imagesToLoad = document.querySelectorAll('image[data-src]');
function loadImage(image) {
image.src = image.getAttribute('data-src');
image.addEventListener('load', function() {
image.removeAttribute('data-src');
});
}
var intersectionObserver = new IntersectionObserver(function(items, observer) {
items.forEach(function(item) {
/* 所有屬性:
item.boundingClientRect - 目標(biāo)元素的幾何邊界信息
item.intersectionRatio - 相交比 intersectionRect/boundingClientRect
item.intersectionRect - 描述根和目標(biāo)元素的相交區(qū)域
item.isIntersecting - true(相交開始)谋作,false(相交結(jié)束)
item.rootBounds - 描述根元素
item.target - 目標(biāo)元素
item.time - 時(shí)間原點(diǎn)(網(wǎng)頁(yè)在窗口加載完成時(shí)的時(shí)間點(diǎn))到交叉被觸發(fā)的時(shí)間的時(shí)間戳
*/
if (item.isIntersecting) {
loadImage(item.target);
observer.unobserve(item.target);
}
});
});
imagesToLoad.forEach(function(image) {
intersectionObserver.observe(image);
});
})();
3. 網(wǎng)站最佳實(shí)踐
? 使用 hexo-abbrlink 插件生成文章鏈接
hexo 框架生成的博客地址默認(rèn)是 :year/:month/:day/:title
這種格式的,也就是 /年/月/日/標(biāo)題
乎芳。當(dāng)博客標(biāo)題為中文時(shí)遵蚜,生成的url鏈接中也出現(xiàn)中文,中文路徑對(duì)于搜索引擎優(yōu)化不友好奈惑。復(fù)制后的鏈接會(huì)被編碼吭净,非常不利于閱讀,也不簡(jiǎn)潔肴甸。
使用 hexo-abbrlink 可以解決這個(gè)問題寂殉,安裝插件:npm install hexo-abbrlink --save
,在 _config.yml
中添加配置:
permalink: :year/:month/:day/:abbrlink.html/
permalink_defaults:
abbrlink:
alg: crc32 # 算法:crc16(default) and crc32
rep: hex # 進(jìn)制:dec(default) and hex
之后生成的博客文章就會(huì)變成這種:https://post.zz173.com/posts/8ddf18fb.html/
原在,即使更新了文章標(biāo)題友扰,文章的鏈接也不會(huì)改變。
? 使用 hexo-filter-nofollow 規(guī)避安全風(fēng)險(xiǎn)
hexo-filter-nofollow 插件會(huì)為所有 <a>
鏈接添加屬性 rel="noopener external nofollow noreferrer"
庶柿。
網(wǎng)站內(nèi)部有大量的外鏈會(huì)影響網(wǎng)站的權(quán)重,不利于SEO村怪。
-
nofollow
:是 Google、Yahoo 和微軟公司前幾年一起提出的一個(gè)屬性澳泵,鏈接加上這個(gè)屬性后就不會(huì)被計(jì)算權(quán)值实愚。nofollow 告訴爬蟲無(wú)需追蹤目標(biāo)頁(yè),為了對(duì)抗 blogspam(博客垃圾留言信息)兔辅,Google推薦使用nofollow,告訴搜索引擎爬蟲無(wú)需抓取目標(biāo)頁(yè)击喂,同時(shí)告訴搜索引擎無(wú)需將的當(dāng)前頁(yè)的Pagerank傳遞到目標(biāo)頁(yè)维苔。但是如果你是通過 sitemap 直接提交該頁(yè)面,爬蟲還是會(huì)爬取懂昂,這里的nofollow只是當(dāng)前頁(yè)對(duì)目標(biāo)頁(yè)的一種態(tài)度介时,并不代表其他頁(yè)對(duì)目標(biāo)頁(yè)的態(tài)度。 -
noreferrer
和noopener
:當(dāng)<a>
標(biāo)簽使用target="_blank"
屬性鏈接到另一個(gè)頁(yè)面時(shí)凌彬,新頁(yè)面將與您的頁(yè)面在同一個(gè)進(jìn)程上運(yùn)行沸柔。如果新頁(yè)面正在執(zhí)行開銷極大的 JavaScript,舊頁(yè)面性能可能會(huì)受影響铲敛。并且新頁(yè)面可以通過window.opener
拿到舊頁(yè)面窗口對(duì)象執(zhí)行任意操作褐澎,具有極大的安全隱患。使用noopener
(兼容屬性noreferrer
) 之后伐蒋,新打開的頁(yè)面就不能拿到舊頁(yè)面窗口對(duì)象了工三。 -
external
:告訴搜素引擎迁酸,這是非本站的鏈接,這個(gè)作用相當(dāng)于target=“_blank”
俭正,減少外部鏈接的 SEO 權(quán)重影響奸鬓。
? 使用 hexo-deployer-git 和 github workflow 進(jìn)行自動(dòng)化部署
靜態(tài)資源打包生成完成后,我需要將其提交到對(duì)應(yīng)的 github pages
或 gitee pages
倉(cāng)庫(kù)中掸读,當(dāng)需要部署多個(gè)倉(cāng)庫(kù)時(shí)串远,手動(dòng)操作效率非常低。因此這里采用 hexo-deployer-git
插件進(jìn)行自動(dòng)化部署儿惫,可以向下面一樣聲明需要部署的倉(cāng)庫(kù)信息抑淫,如果有多個(gè)倉(cāng)庫(kù)直接聲明多個(gè) deploy
字段即可:
# Deployment
deploy:
type: git
repository: https://github.com/nojsja/blogs
branch: master
ignore_hidden:
public: false
message: update
值得說明的是,非付費(fèi)用戶 gitee pages
倉(cāng)庫(kù)不支持提交后自動(dòng)部署姥闪。因此我采用的方案是只聲明一個(gè) deploy
部署倉(cāng)庫(kù)指向 github pages
倉(cāng)庫(kù)始苇,然后再通過 github 倉(cāng)庫(kù)自帶的 github workflow
服務(wù)配合 gitee-pages-action 這個(gè)腳本實(shí)現(xiàn)的 gitee 自動(dòng)部署。它的原理就是使用 github 自動(dòng)化工作流將 github 倉(cāng)庫(kù)的代碼同步到 gitee 倉(cāng)庫(kù)中去筐喳,然后通過讀取我們配置的 gitee 賬戶信息自動(dòng)執(zhí)行登錄 gitee 賬戶并調(diào)用網(wǎng)頁(yè)的手動(dòng)部署接口催式,實(shí)現(xiàn)整個(gè)自動(dòng)化部署流程。
4. 網(wǎng)站SEO優(yōu)化
? 使用 hexo-generator-sitemap 插件自動(dòng)生成網(wǎng)站地圖
站點(diǎn)地圖是什么:
- 站點(diǎn)地圖描述了一個(gè)網(wǎng)站的結(jié)構(gòu)避归。它可以是一個(gè)任意形式的文檔荣月,用作網(wǎng)頁(yè)設(shè)計(jì)的設(shè)計(jì)工具,也可以是列出網(wǎng)站中所有頁(yè)面的一個(gè)網(wǎng)頁(yè)梳毙,通常采用分級(jí)形式哺窄。這有助于訪問者以及搜索引擎的機(jī)器人找到網(wǎng)站中的頁(yè)面。
- 一些開發(fā)者認(rèn)為網(wǎng)站索引是組織網(wǎng)頁(yè)的一種更合適的方式账锹,但是網(wǎng)站索引通常是A-Z索引萌业,只提供訪問特定內(nèi)容的入口,而一個(gè)網(wǎng)站地圖為整個(gè)站點(diǎn)提供了一般的自頂向下的視圖奸柬。
- 網(wǎng)站地圖讓所有頁(yè)面可被找到來(lái)增強(qiáng)搜索引擎優(yōu)化的效果生年。
安裝:
$: npm install hexo-generator-sitemap --save
配置文件_config.yml
中添加相關(guān)字段:
# sitemap
sitemap:
path: sitemap.xml
# page url
url: https://nojsja.github.io/blogs
之后運(yùn)行 hexo generate
之后就可以自動(dòng)生成網(wǎng)站地圖 sitemap.xml
了,接下來(lái)需要到 Google Search Console
記錄自己的站點(diǎn)并提交相應(yīng)的站點(diǎn)文件廓奕。
? 向 Google Search Console 提交個(gè)人網(wǎng)站地圖
- 登錄 Google Search Console
-
添加自己的網(wǎng)站信息
-
可以通過幾種方法驗(yàn)證所有權(quán)
- 提交插件生成的
sitemap.xml
Google Search Console
還能看到自己網(wǎng)站的點(diǎn)擊情況抱婉、關(guān)鍵詞索引情況等,非常方便桌粉。
參考
結(jié)語(yǔ)
學(xué)習(xí)前端性能優(yōu)化的方方面面蒸绩,一方面是對(duì)我們核心基礎(chǔ)知識(shí)的考察,另一方面也能為我們遇到的一些實(shí)際問題提供處理思路铃肯,是每個(gè)前端人進(jìn)階的的必經(jīng)之路患亿。
以上就是本篇文章的所有內(nèi)容,后續(xù)有需要還會(huì)繼續(xù)更新…