hexo个人网站优化探究

  • 时间:2022-03-15 14:23 作者:nojsja 来源: 阅读:373
  • 扫一扫,手机访问
摘要: 原文链接Contents## Contents## 前言1. hexo 是什么?2. 何为优化?## 性能优化指标1. 整体运行性能2. 网站可访问性3. 网站能否应用了最佳实践策略 1) 使用 target=”_blank” 的 a 链接假如没有公告 rel=”noopener nor

>> 原文链接

Contents


  • ## Contents
  • ## 前言
    • 1. hexo 是什么?
    • 2. 何为优化?
  • ## 性能优化指标
    • 1. 整体运行性能
    • 2. 网站可访问性
    • 3. 网站能否应用了最佳实践策略
      • > 1) 使用 target="_blank"<a> 链接假如没有公告 rel="noopener noreferrer" 存在安全风险。
      • > 2) 检查浏览器端控制台能否有告警和错误提醒,通过指示定位问题并处理。
      • > 3) http 和 https 协议地址不混用
      • > 4) 避免使用 AppCache
      • > 5) 避免使用 document.write()
      • > 6) 避免使用 mutation events
      • > 7) 避免使用 Web SQL
      • > 8) 避免加载过于庞大的 DOM 树
      • > 9) 允许客户粘贴密码
    • 4. 网站搜索引擎优化SEO
  • ## 性能测试工具 lighthouse
  • ## 基于 hexo 框架的网站优化
    • 1. 优化资源加载时间
      • ➣ 使用 defer/async 异步下载 script 脚本资源
      • ➣ 使用 async 函数异步加载外部资源
      • ➣ 使用浏览器 onfocus 事件推迟外部资源加载
      • ➣ 使用 preload/prefetch/preconnect 进行预加载优化
      • ➣ 使用 hexo 插件压缩代码文件和图片文件
      • ➣ 编写 hexo-img-lazyload 插件添加图片懒加载特性
      • ➣ 使用 IntersectionObserver API 懒加载其它资源
      • ➣ 使用 CDN 加载外部依赖脚本
      • ➣ 使用 Aplayer 替代 iframe 加载网易云音乐
    • 2. 优化界面运行性能
      • ➣ 优化页面的回流和重绘情况
      • ➣ 使用节流和去抖思想优化滚动事件监听
      • ➣ IntersectionObserver API 的 polyfill 兼容策略
      • ➣ 使用 IntersectionObserver 替代原生 onscroll 事件监听
    • 3. 网站最佳实践
      • ➣ 使用 hexo-abbrlink 插件生成文章链接
      • ➣ 使用 hexo-filter-nofollow 规避安全风险
    • 4. 网站SEO优化
      • ➣ 使用 hexo-generator-sitemap 插件自动生成网站地图
      • ➣ 向 Google Search Console 提交个人网站地图
  • ## 参考
  • ## 结语

前言


1. hexo 是什么?

hexo 是一个为了不依赖于后台进行界面实时数据查询展现而设计的网站开发工具。比方之前曾使用 Node.js 作为后端开发过一个博客网站,自己实现后端逻辑的话需要考虑数据库存储、前后台交互、后台 Server 部署啥的。整个流程比较繁杂,在初期可以作为前台开发者个人建站学习的一种方式。hexo 简化了这一流程,它将数据存储和数据获取这两方面都通过编译构建而后本地化集成到前台静态资源里。一个例子就是博客网站通常需要翻页功能来获取博客文章,传统开发方式下,获取下一页这个操作由前台脚本发起,并由后台 Server 解决请求并返回,但是使用 hexo 之后这整个过程都是在本地一次完成的,hexo 将所有静态资源在本地建立了索引。

使用 hexo 通常写手只要要关注 markdown 格式文章的编写,其他的网站编译、构建和发布的流程都可以交由框架进行解决,所有的网站内容均会被打包成静态的 html/css/js 文件。hexo 支持自己设置插件,也有一个插件社区,假如写手同时具有前台能力的话也可以发布自己的插件到社区里进行开源共享。

2. 何为优化?

我们通常所讲的性能高低可能侧重于对网站运行速度快慢的评估,其包括静态资源及脚本获取的速度和网站UI界面能否运行流畅。其实广义上的优化应包括:网站性能优化、网站可访性优化、网站SEO优化、网站最佳实践等。

性能优化指标


1. 整体运行性能

  • FCP (First Contentful Paint):从客户开始发起网站请求到浏览器第一次开始渲染网站数据的所用时长。其中提及的第一次开始渲染的网站数据包含网页的文字、图片、HTML DOM 结构等,而不包含位于 iframe 中的网页数据。该指标通常用于衡量本地和服务器初次建立网络通讯的速度。

  • TTI (Time To Interactive):从客户开始导航至网站到页面变为完全可交互所花费的时间。网站可交互的衡量标准就是:网站展现了实际可用的内容、界面上可见元素的网页事件已经被成功绑定(比方点击、拖动等事件)、客户和页面交互的反馈时间低于 50 ms。

  • SI (Speed Index):衡量页面加载过程中内容可视化显示的速度。浅显来讲就是网站界面元素的绘制和呈现速度,假如使用 lighthouse 测量工具的话它会捕获浏览器中处于加载中的页面的多个图片帧,而后计算帧之间的视觉渲染进度。

  • TBT (Total Blocking Time):衡量从页面初次开始渲染(FCP)之后到页面实际可交互(TTI)的时间。通常我们访问一个网站时网站整体呈现后,有一段较短的时间我们不能和界面进行交互,比方鼠标点击、键盘按键等,这段时间浏览器在进行脚本及样式的加载和执行。

  • LCP (Largest Contentful Paint):测量视口中最大的内容元素何绘制到屏幕
    所需的时间。通常包含这个元素的下载、解析和渲染整个过程。

  • CLS (Cumulative Layout Shift):一个衡量网站加载时整体布局抖动情况的数值指标。假如一个网站在加载过程中客户界面屡次抖动和闪烁的话会可能引起客户的轻度不适,因而应该尽量减少网站的重排和重绘次数。

2. 网站可访问性

  • 网页背景色和网站文字前景的比照度不能太低,否则会影响客户阅读。
  • 网页链接标签 <a> 最好包含对链接的形容信息,比方:<a href=" nojsja">[形容- nojsja 的 github 个人界面]</a>
  • html 元素存在 lang 属性指定当前语言环境。
  • 正确的 html 语义化标签能让键盘和读屏器正常工作,通常一个网页的结构可以用语义化标签形容为:
<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 href="https://google.com">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 标签的 alt 属性公告。它指定了替代文本,用于在图像无法显示或者者客户禁用图像显示时,代替图像显示在浏览器中的内容。
  • form 元素内部公告 label 标签以让读屏器正确工作。
  • iframe 元素公告 title 属性来形容其内部内容以便于读屏器工作。
  • aria 无障碍属性和标签的使用,相关参考 >> aria reference
  • input[type=image]、object 标签增加 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 属性,当我们按 tab 键时焦点会依次切换。并且根据键盘序列导航的顺序,值为 0 、非法值、或者者没有 tabindex 值的元素应该放置在 tabindex 值为正值的元素后面:
1) tabindex=负值 (通常是tabindex=“-1”),表示元素是可聚焦的,但是不能通过键盘导航来访问到该元素,用JS做页面小组件内部键盘导航的时候非常有用。2) tabindex="0" ,表示元素是可聚焦的,并且可以通过键盘导航来聚焦到该元素,它的相对顺序是当前处于的DOM结构来决定的。3) tabindex=正值,表示元素是可聚焦的,并且可以通过键盘导航来访问到该元素;它的相对顺序按照tabindex 的数值递增而滞后获焦。假如多个元素拥有相同的 tabindex,它们的相对顺序按照他们在当前DOM中的先后顺序决定。
  • table 元素中正确使用 thscope 让行表头和列表头与其数据域逐个对应:
<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 列表标签放在容器组件 ul 或者 ol 中使用。
  • heading 标签严格按照升序公告,配合 section 或者其它元素(例如p标签)正确反映界面内容结构:
<h1>Page title</h1><section>  <h2>Section Heading</h2>  …    <h3>Sub-section Heading</h3></section>
  • 使用 <meta charset="UTF-8"> 指定网站字符集编码。
  • img 元素引用的图片资源长宽比应该和 img 当前应用的长宽比相同,不然可能造成图片扭曲。
  • 增加 <!DOCTYPE html> 以防止浏览界面渲染异常。

3. 网站能否应用了最佳实践策略

> 1) 使用 target="_blank"<a> 链接假如没有公告 rel="noopener noreferrer" 存在安全风险。

当页面链接至使用 target="_blank" 的另一个页面时,新页面将与旧页面在同一个进程上运行。假如新页面正在执行开销极大的 JavaScript,旧页面性能可能会受影响。并且新的页面可以通过 window.opener 访问旧的窗口对象,比方它可以使用 window.opener.location = url 将旧页面导航至不同的网址。

> 2) 检查浏览器端控制台能否有告警和错误提醒,通过指示定位问题并处理。

> 3) http 和 https 协议地址不混用

浏览器已经逐步开始禁止不用协议资源的混用,比方使用 http 协议的 web 服务器加载 https 协议开头的资源,因而可能出现以下几种情况:

  • 加载了混合内容,但会出现警告;
  • 不加载混合内容,直接会显示空白内容;
  • 在加载混合内容之前,会出现相似能否“显示”,或者存在不安全风险而被“阻止”的提醒!

应该考虑以下方式进行优化:

  • 将站点加载的部分协议混用外部资源放入自己的服务器进行托管;
  • 对于部署了 https 的网站,在网页公告 <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"将 http 请求转换成 https 请求;
  • 对于部署了 https 的网站,在服务器端增加请求首部:header("Content-Security-Policy: upgrade-insecure-requests")也可以达到一样的效果;
  • 对于同时支持 http 和 https 访问的网站,考虑使用相对协议,而不直接明文指定协议:<script src="//path/to/js">,浏览器发送请求时会根据当前页面协议进行动态切换。
  • 类同于使用相对协议,使用相对URL也能达到目的,不过添加了资源之间的耦合度:<script src="./path/to/js"></script>

> 4) 避免使用 AppCache

AppCache已被废弃 考虑使用service worker的Cache API,

> 5) 避免使用 document.write()

对于网速较慢(2G、3G或者较慢的WLAN)的客户,外部脚本通过document.write()动态注入会使页面内容的显示推迟数十秒。

> 6) 避免使用 mutation events

以下mutation events会损害性能,在DOM事件规范中已经弃用:

  • DOMAttrModified
  • DOMAttributeNameChanged
  • DOMCharacterDataModified
  • DOMElementNameChanged
  • DOMNodeInserted
  • DOMNodeInsertedIntoDocument
  • DOMNodeRemoved
  • DOMNodeRemovedFromDocument
  • DOMSubtreeModified

建议使用 MutationObserver 替代

> 7) 避免使用 Web SQL

建议替换为IndexedDB

> 8) 避免加载过于庞大的 DOM 树

大型的DOM树会以多种方式降低页面性能:

  • 网络效率和负载性能,假如你的服务器发送一个大的DOM树,你可能会运送大量不必要的字节。这也可能会减慢页面加载时间,由于浏览器可能会解析许多没有显示在屏幕上的节点。
  • 运行时性能。当客户和脚本与页面交互时,浏览器必需不断重新计算节点的位置和样式。一个大的DOM树与复杂的样式规则相结合可能会严重减慢渲染速度。
  • 内存性能。假如使用通用查询选择器(例如,document.querySelectorAll('li') 您可能会无意中将引用存储到大量的节点),这可能会压倒客户设施的内存功能。

一个最佳的DOM树:

  • 总共少于1500个节点。
  • 最大深度为32个节点。
  • 没有超过60个子节点的父节点。
  • 一般来说,只要要在需要时寻觅创立DOM节点的方法,并在不再需要时将其销毁。

> 9) 允许客户粘贴密码

密码粘贴提高了安全性,由于它使客户能够使用密码管理器。密码管理员通常为客户生成强密码,安全地存储密码,而后在客户需要登录时自动将其粘贴到密码字段中。
删除阻止客户粘贴到密码字段的代码。使用事件断点中的Clipboard paste来打断点,可以快速找到阻止粘贴密码的代码。比方下列这种阻止粘贴密码的代码:

var input = document.querySelector('input');input.addEventListener('paste', (e) => {  e.preventDefault();});

4. 网站搜索引擎优化SEO

  • 增加视口公告 <meta name="viewport"> 并指定 with 和 device-width 来优化手机端显示。
  • document 增加 title 属性以让读屏器和搜索引擎正确识别网站内容。
  • 增加 meta desctiption 标签来形容网站内容 <meta name="description" content="Put your description here.">
  • 为链接标签增加形容文本 <a href="videos.html">basketball videos</a>,以清楚传达这个超链接的指向内容。
  • 使用正确的 href 链接地址以让搜索引擎正确跟踪实际网址,以下是反例:<a onclick="goto('https://example.com')">
  • 不要使用 meta 标签来禁用搜索引擎爬虫爬取你的网页,以下是反例:<meta name="robots" content="noindex"/>,相对的可以特殊的排除少量爬虫爬取:<meta name="AdsBot-Google" content="noindex"/>
  • image 图片元素中使用具备明确用意和含义的 alt 属性文字来说明图片信息,并且避免使用少量非特定指代词比方: "chart", "image", "diagram"(图表、图片)。
  • 不要使用插件来显示您的内容,即避免使用 embedobjectapplet等标签来引入资源。
  • 正确放置 robots.txt 到网站根目录下,它形容了此网站中的哪些内容是不应被搜索引擎的获取的,哪些是可以被获取的。通常少量后端文件(如css、js)和客户隐私的文件不用被搜索引擎抓取,另外有些文件频繁被蜘蛛抓取,但是这些文件又是不重要的,那么可以用robots.txt进行屏蔽。
    一个实例:
User-agent: *Allow: /Disallow: /wp-admin/Disallow: /wp-includes/Disallow: /wp-content/plugins/Disallow: /?r=*
  • 向搜索引擎提交网站地图 sitemap.xml,xml格式的文本就是专门拿来给电脑进行阅读的语,而 sitemap.xml就是搜寻引擎利用这个规范,让网站主可以使用它来制作一个包含了网站内所有网页的目录档案,提供给搜寻引擎的爬虫阅读。就像是一个地图一样,让搜寻引擎可以知道网站内究竟有些什么网页。
<?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>

性能测试工具 lighthouse


以上提到的性能监测指标通过 lighthouse 网站性能监测工具可以自动化分析和生成性能测试结果数据,较新版本的 chrome 和 采用 chromium 架构的 edge 浏览器都已经自带了,较低版本的 chrome 浏览器可以通过在商店搜索插件安装。安装后使用 F12 打开控制台,切换到 lighthouse 一栏就可直接使用了:

如图,可以自行决定勾选测试项目和针对测试平台(桌面/手机端),最后点击生成报告就可开始运行自动化测试任务,并在测试完成后打开一个分析结果页面:

lighthouse

结果界面:

lighthouse-test

至此我们就能对网站的整体性能进行少量评估了,上图中的PerformanceAccessibilityBest Practices、和 SEO 依次对应上文我们提及的网站整体性能、网站可访问性、网站最佳实践、搜索引擎SEO优化等指标。我们可以点击每一个具体项目来查看测试工具给出的优化建议:

image

测试结果很大程度上取决于你的 http 静态资源服务器的资源加载速度,比方在不使用代理商的情况下,使用 github pages 服务来托管静态资源会比使用国内的 gitee pages 托管服务稍慢,而且可能出现部分资源加载失败的问题。因而国内客户可以使用 gitee pages 来替代 github pages,不过 gitee 非付费客户没有代码自动构建部署功能,需要使用下文提到的 github action 来进行自动化登录并触发构建和部署。

注意: 某些浏览器上安装的插件会影响测试结果,由于插件可能会发起请求加载其它脚本啥的。这种情况下可以直接使用 npm 包管理器全局安装 npm install -g lighthouse,而后使用命令行启动测试流程:lighthouse https://nojsja.gitee.io/blogs --view --preset=desktop --output-path='/home/nojsja/Desktop/lighthouse.html'。最终会根据指定的地址生成一个可直接浏览器测试结果网页文件 lighthouse.html,可以直接打开进行性能排查。

基于 hexo 框架的网站优化


之前写的一篇文章 《前台性能优化技巧详解(1)》,详细的形容了前台性能优化的少量方面,这篇文章不会再罗列每一点,只会对博客中实际应用的优化手段进行说明,大致被划分成这几个方面:

  1. 优化资源加载时间
  2. 优化界面运行性能
  3. 网站最佳实践
  4. 网站 SEO 优化

1. 优化资源加载时间

常见的加载时间优化方式包含以下:

  • 提高网页资源并行加载能力
  • 推迟加载不必要的外部资源
  • 减少碎片化的外部资源请求,小文件做合并
  • 添加网站带宽

➣ 使用 defer/async 异步下载 script 脚本资源

HTML在解析时遇到公告的<script>脚本会立即下载和执行,往往会推迟界面剩余部分的解析,造成界面白屏的情况。比较古老的优化方式之一就是将脚本放到HTML文档末尾,这样子处理了白屏的问题,可是在DOM文档结构复杂冗长时,也会造成肯定的界面脚本下载和执行推迟,script标签新属性async和defer可以处理此类问题:

  • defer脚本:公告 defer 属性的外部<script>脚本下载时不会阻塞HTML的解析和渲染,并且会在HTML渲染完成并且可实际操作之后开始执行(DOMContentLoaded事件被触发之前),各个脚本解析执行顺序对应公告时的位置顺序,执行完成后会触发页面DOMContentLoaded事件。
  • async脚本:公告 async 属性的外部<script>脚本下载时不会阻塞HTML的解析和渲染,各个脚本的下载和执行完全独立,下载完成后即开始执行,所以执行顺序不固定,与DOMContentLoaded事件的触发没有关联性。

在我的博客网站中有使用 Bootstrap 外部依赖的样式和js脚本,但是需要确保他们的公告顺序在靠前的位置,由于使用异步技术之后,内联同步执行的其它<script>脚本的执行顺序就不能保证了,因而不能使用 defer/async 属性进行优化。

通常 async/defer 会用于优化少量独立的子组件依赖脚本的加载,比方用于博客文章中的导航条跳转的脚本,它的执行顺序完全不收到其它部分的制约,因而可以独立出来使用 async 属性进行优化。但是需要确保该脚本作用的 导航条 DOM元素公告位于脚本引入位置之前,以防止出现脚本执行时 DOM 元素还未渲染的状态,引起脚本错误。

➣ 使用 async 函数异步加载外部资源

以下 async 函数作用就是根据传入的 url 创立 link/script 标签并增加到 <head> 标签中以动态加载外部资源,并且在存在回调函数时监听资源加载情况,加载完毕后再执行回调函数。值得注意的是本方法与直接通过script 公告依赖资源的不同之处在于不会阻塞界面,脚本的下载和执行都是异步的。

博客中常用于在某个特殊的情况下利用编程方法动态载入外部依赖库,并在回调函数内进行外部库的初始化。例如我的博客使用了一个音乐播放器组件,在网页可视区域滚动到包含这个组件的尚未初始化的 DOM 元素时,就是用 async 来请求服务器的 js 脚本资源,加载完成后在回调函数里初始化这个音乐播放器。

/**  * async [异步脚本加载]  * @author nojsja  * @param  {String} u [资源地址]  * @param  {Function} c [回调函数]  * @param  {String} tag [加载资源类型 link | script]  * @param  {Boolean} async [加载 script 资源时能否需要公告 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 事件推迟外部资源加载

通过客户和界面交互触发少量事件后,满足了外部资源加载的条件,再触发外部资源的加载,也属于推迟加载的一种优化。

例如我的博客中右侧导航条区域有个搜索框可以搜索博客文章,本身这个搜索是通过查找本地预先生成的一个资源索引静态XML文件来实现的。文章和内容较多这个这个XML文件就会变得庞大,假如在网页初次加载时就下载它肯定会造成网页带宽和网络请求数量的占用。因而考虑在客户点击搜索框将焦点集中到上面时再触发XML的异步下载,同时为了不影响使用体验,可以在加载过程中设置 loading 效果以指示客户推迟操作。

➣ 使用 preload/prefetch/preconnect 进行预加载优化

  • preload 用来预加载当前页面所需的资源,如图像,CSS,JavaScript 和字体文件,它的加载优先级比 prefetch 更高,同时也要注意 preload 并不会阻塞 window 的 onload 事件。博客中有使用它来预加载 css 中引用的字体:<link href="/blogs/fonts/fontawesome-webfont.woff2?v=4.3.0" rel=preload as="font">,针对不同的资源类型需要增加不同的 as 标记信息,假如是跨域加载的话注意增加 crossorigin 属性公告。
  • prefetch 是一个低优先级的资源提醒,一旦一个页面加载完毕就会开始下载其余的预加载资源,并且将他们存储在浏览器的缓存中。其中 prefretch 又包含:link、DNS 和 prerendering 三中类型的预加载请求。link prefetch 比方:<link rel="prefetch" href="/path/to/pic.png"> 允许浏览器获取资源并将他们存储在缓存中;DNS prefetch 允许浏览器在客户浏览页面时在后端运行 DNS prerender 和 prefetch 非常类似,它们都优化了下一页资源的未来请求,区别是 prerender 在后端渲染了整个页面,因而应该小心使用,可能会造成网络带宽的白费。
  • preconnect 允许浏览器在一个 HTTP 请求正式发给服务器前预先执行少量操作,这包括 DNS 解析,TLS 协商,TCP 握手,这消除了往返推迟并为客户节省了时间。比方博客中:不算子统计库的网页预连接 <link href="http://busuanzi.ibruce.info" rel="preconnect" crossorigin>

➣ 使用 hexo 插件压缩代码文件和图片文件

压缩静态资源也是一种节省网络带宽,提高请求响应速度的方式。通常采用工程化的方式进行配置,而不用手动对每张图片进行压缩。我的博客中使用了 hexo 的一款压缩插件 Hexo-all-minifier,通过压缩 HTML、CSS、JS 和图片来优化博客访问速度。

安装:

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 # 压缩等级  pngquant: false  progressive: true # 能否启用渐进式图片压缩

资源的压缩比较消耗性能和时间,可以考虑在开发环境下不启用这些插件以加快开发环境启动。比方单独指定一个 _config.dev.yml 而后把上述插件一律关闭就可,参考 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 插件添加图片懒加载特性

在进行博客优化的时候为了学习 hexo 自己的插件系统,使用 IntersectionObserver API 来编写了一个图片懒加载插件:hexo-img-lazyload,可以通过 npm 命令进行安装:npm i hexo-img-lazyload

效果预览:

hexo-immg-lazyload

插件主要原理就是监听博客构建流程的钩子事件,拿到构建好的代码字符串,而后代码中原生的图片公告比方:<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 的代码注入功能将我们自己编写的代码注入到每个构建好的界面中。

hexo 代码注入:

/* registry scroll listener */hexo.extend.injector.register('body_end', function() {  const script = `    <script>      ${observerStr}    </script>`;  return script;}, injectionType)

被注入的用于监听待加载图片元素能否进入可视区域以进行动态加载的部分代码,使用了 IntersectionObserver API 而不是 window.onscroll 事件的方式,前者具备更好的性能,由浏览器统一监听所有元素位置信息更改并分发滚动事件结果:

(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因为性能更好已经在我的博客中作为一种主要的资源懒加载方式,我还使用它来优化博客评论组件 Valine 的加载。一般评论组件都位于博客文章的最下方,因而刚载入文章页面时完全没有必要进行评论组件的资源加载,可以考虑使用 IntersectionObserver 监听评论组件能否进入视口,进入后再使用 async 异步脚本下载并回调执行评论系统初始化。

另一方面每篇文章底部的音乐播放器 Aplayer 也使用了相似的加载策略,可以说优化效果屡试不爽!

➣ 使用 CDN 加载外部依赖脚本

CDN 即内容分发网络。CDN 服务商将静态资源缓存到遍布全国的高性能加速节点上,当客户访问相应的业务资源时,CDN系统能够实时地根据网络流量和各节点的连接负载状况、到客户的距离和响应时间 等综合信息将客户的请求重新导向离客户最近的服务节点上,使内容能够传输的更快,更加稳固。可以提升初次请求的响应能力。

博客中少量公用外部库比方 BootstrapjQuery 都是使用的外部 CDN 资源地址,一方面可以减小当前主站的网页带宽消耗,另一方面 CDN 也能提供少量资源下载加速。

➣ 使用 Aplayer 替代 iframe 加载网易云音乐

博客之前的版本会在文章界面的底部嵌入一个网易云音乐自己的播放器,这个播放器其实是一个 iframe 像这样:

<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=781246&auto=1&height=66"></iframe>

iframe 加载的时候会加载一堆东西,尽管可以通过 lazy 属性来进行懒加载,不过iframe 也有很多缺点:

  • iframe会阻塞主页面的onload事件
  • iframe和主页面共享HTTP连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载
  • iframe不利于网页布局
  • iframe对手机端不友好
  • iframe的反复重新加载可能导致少量浏览器的内存泄露
  • iframe中的数据传输复杂
  • iframe不利于SEO

新版本将 iframe 播放器换成了 Aplayer 并且把自己喜欢的一个歌曲列表上传到了另一个 gitee pages 仓库 进行静态托管,可以通过以下方式在博客底部加载一个自己设置歌曲列表:

var ap = new APlayer({  container: document.getElementById('aplayer'),  theme: '#e9e9e9',  audio: [{    name: '存在信号',    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: '遺サレタ場所/斜光',    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'  }]});

预览图:

aplayer

2. 优化界面运行性能

➣ 优化页面的回流和重绘情况

1)概念

回流(重排,reflow)和重绘(repaint)是浏览器渲染网页必不可少的一个过程,回流主要HTML渲染过程中元素空间位置和大小改变引起的 DOM 树重新渲染,而重绘是因为节点的样式属性发生改变,并不会影响空间布局。从性能而言回流的性能消耗大,而且容易产生级联效应,即一个正常 DOM 树的流布局中,一个元素发生回流后,该元素位置之后的元素一律都会发生回流并重新渲染,重绘相对性能消耗更小。

2)怎么有效判断界面的回流和重绘情况?

其实一般基于 chromium 架构浏览器都附带一个网页开发工具 Devtools,但可以说绝大多数前台开发者都没有认真理解过这个工具的具体用途,只是把它用作简单的 log调试、网页请求追踪和样式调试这些基础功能。回流和重绘也是可以通过它来进行可视化度量的:F12打开 Devtools,找到右上角三个点的折叠按钮依次打开 -> More Tools(更多工具) -> Rendering (渲染) - 勾选前两项 Paint Flashing (高亮重绘区域) 和 Layout Shift Regions (高亮回流区域),现在重新回到你打开的页面进行操作,操作过程中发生了回流的区域会变成蓝色,发生了重绘的区域会变成绿色,持续时间不长,注意观察。

Devtools

Repaint:

Repaint

Reflow:

Reflow

除了用于可视化界面回流/重绘的情况,Devtools 还有其余少量很实用的功能,比方:Coverage Tools 可以用于分析界面上引入的外部 css/js 脚本内容的使用覆盖率,就是说我们能通过这个工具衡量引入的外部文件的使用情况,使用频次较低的外部资源可以考虑内联或者是直接手写实现,提升引入外部资源的性价比

➣ 使用节流和去抖思想优化滚动事件监听

在面对少量需要进行调用控制的函数高频触发场景时,可能有人会对何时使用节流何时使用去抖产生疑问。这里通过一个特性进行简单区分:假如你需要保留短时间内高频触发的最后一次结果时,那么使用去抖函数,假如你需要对函数的调用次数进行限制,以最佳的调用间隔时间保持函数的持续调用而不关心能否是最后一次调用结果时,请使用节流函数。

比方 echarts 图常常需要在窗口 resize 之后重新使用数据渲染,但是直接监听 resize 事件可能导致短时间内渲染函数被触发屡次。我们可以使用函数去抖的思想,监听 resize 事件后在监听器函数里获取参数再使用参数调用事前初始化好了的 throttle 函数,保证 resize 过程结束后能触发一次实际的 echarts 重渲染就可。

这里给出节流函数和去抖函数的简单实现:

  /**    * fnDebounce [去抖函数]    * @author nojsja    * @param  {Function} fn [需要被包裹的原始函数逻辑]    * @param  {Numberl} timeout [推迟时间]    * @return {Function} [高阶函数]    */  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 [节流函数]    * @author nojsja    * @param  {Function} fn [需要被包裹的原始函数逻辑]    * @param  {Numberl} timeout [推迟时间]    * @return {Function} [高阶函数]    */  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));      }    };  };

博客中文章右侧的内容导航栏会根据滚动条位置自动切换 fixed 布局和一般流布局,这么做是为了让导航栏在阅读文章过程中也能正常呈现,不会被隐藏到顶部:

  /* 限150ms才能触发一次滚动检测 */  (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 已经用于博客中各个界面组件的懒加载功能,它的性能更好、功能也更加全面。但是网页开发中我们通常会考虑各个 API 的兼容性,这里可以通过 Can I Use 这个兼容性报告网站进行查看,从下图可知这个 API 的兼容情况还是可以的,桌面端很多较高版本浏览器均已支持:

caniuse

因而为理解决个别低版本浏览器的兼容性问题,这里采用了一种比较极端的解决方式。常规情况下我们需要引入外部 [xxx].polyfill.js (xxx为相应的 API) 来为低版本浏览器增加相应功能,但是对于高版本浏览器自身已经支持了这个 API,却要重复下载 polyfill 库,造成网页请求数和带宽资源的白费。因而我这里不采用这种方式,由于这个 API 大部分浏览器已经支持,我们默认不使用 <script> 标签引入 polyfill.js 而是通过脚本判断当前浏览器能否支持此 API,假如不支持的话再使用同步XHR请求远程 下载polyfill 文件,下载后使用 eval(...) 的方式执行整个脚本。使用同步方式会阻塞当前 js 执行线程,请谨慎使用,此处是为了保证 IntersectionObserver 以高优先级方式被注入到网页中,不然可能引发少量使用了此 API 脚本错误。

<!-- 此脚本被放置在靠近页面首部某个位置 --><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 事件监听

IntersectionObserver 通常用于界面中的少量相交检测场景:

  • 图片懒加载 – 当图片滚动到可见时才进行加载
  • 内容无限滚动 – 客户滚动到接近滚动容器底部时直接加载更多数据,而无需客户操作翻页,给客户一种网页可以无限滚动的错觉
  • 检测广告的曝光情况——为了计算广告收益,需要知道广告元素的曝光情况
  • 在客户看见某个区域时执行任务、播放视频

以内容无限滚动为例,古老的相交检测方案就是使用 scroll 事件监听滚动容器,在监听器函数中获取滚动元素的几何属性判断元素能否已经滚动究竟部。我们知道scrollTop等属性的获取和设置都会导致页面回流,并且假如界面需要绑定多个监听函数到scroll事件进行相似操作的时候,页面性能会大打折扣:

  /* 滚动监听 */  onScroll = () => {    const {       scrollTop, scrollHeight, clientHeight    } = document.querySelector('#target');        /* 已经滚动究竟部 */    // scrollTop(向上滚动的高度);clientHeight(容器可视总高度);scrollHeight(容器的总内容长度)    if (scrollTop + clientHeight === scrollHeight) { /* do something ... */ }  }

这里以一个简单实现的图片懒加载功能来详情下它的使用方式,详细使用可以查看博客:《前台性能优化技巧详解(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 - 目标元素的几何边界信息        item.intersectionRatio - 相交比 intersectionRect/boundingClientRect        item.intersectionRect -  形容根和目标元素的相交区域        item.isIntersecting - true(相交开始),false(相交结束)        item.rootBounds - 形容根元素        item.target - 目标元素        item.time - 时间原点(网页在窗口加载完成时的时间点)到交叉被触发的时间的时间戳      */      if (item.isIntersecting) {        loadImage(item.target);        observer.unobserve(item.target);      }    });  });  imagesToLoad.forEach(function(image) {    intersectionObserver.observe(image);  });  })();

3. 网站最佳实践

➣ 使用 hexo-abbrlink 插件生成文章链接

hexo 框架生成的博客地址默认是 :year/:month/:day/:title 这种格式的,也就是 /年/月/日/标题。当博客标题为中文时,生成的url链接中也出现中文,中文路径对于搜索引擎优化不友好。复制后的链接会被编码,非常不利于阅读,也不简洁。

使用 hexo-abbrlink 可以处理这个问题,安装插件:npm install hexo-abbrlink --save,在 _config.yml 中增加配置:

permalink: :year/:month/:day/:abbrlink.html/permalink_defaults:abbrlink:  alg: crc32  # 算法:crc16(default) and crc32  rep: hex    # 进制:dec(default) and hex

之后生成的博客文章就会变成这种:https://post.zz173.com/posts/8ddf18fb.html/,即便升级了文章标题,文章的链接也不会改变。

➣ 使用 hexo-filter-nofollow 规避安全风险

hexo-filter-nofollow 插件会为所有 <a> 链接增加属性 rel="noopener external nofollow noreferrer"

网站内部有大量的外链会影响网站的权重,不利于SEO。

  • nofollow:是 Google、Yahoo 和微软公司前几年一起提出的一个属性,链接加上这个属性后就不会被计算权值。nofollow 告诉爬虫无需追踪目标页,为了对抗 blogspam(博客垃圾留言信息),Google推荐使用nofollow,告诉搜索引擎爬虫无需抓取目标页,同时告诉搜索引擎无需将的当前页的Pagerank传递到目标页。但是假如你是通过 sitemap 直接提交该页面,爬虫还是会爬取,这里的nofollow只是当前页对目标页的一种态度,并不代表其余页对目标页的态度。
  • noreferrernoopener:当 <a> 标签使用 target="_blank" 属性链接到另一个页面时,新页面将与您的页面在同一个进程上运行。假如新页面正在执行开销极大的 JavaScript,旧页面性能可能会受影响。并且新页面可以通过 window.opener 拿到旧页面窗口对象执行任意操作,具备极大的安全隐患。使用 noopener (兼容属性 noreferrer) 之后,新打开的页面就不能拿到旧页面窗口对象了。
  • external:告诉搜素引擎,这是非本站的链接,这个作用相当于 target=“_blank”,减少外部链接的 SEO 权重影响。

4. 网站SEO优化

➣ 使用 hexo-generator-sitemap 插件自动生成网站地图

站点地图是什么:

  • 站点地图形容了一个网站的结构。它可以是一个任意形式的文档,用作网页设计的设计工具,也可以是列出网站中所有页面的一个网页,通常采用分级形式。这有助于访问者以及搜索引擎的机器人找到网站中的页面。
  • 少量开发者认为网站索引是组织网页的一种更合适的方式,但是网站索引通常是A-Z索引,只提供访问特定内容的入口,而一个网站地图为整个站点提供了一般的自顶向下的视图。
  • 网站地图让所有页面可被找到来加强搜索引擎优化的效果。

安装:

$: npm install hexo-generator-sitemap --save

配置文件_config.yml中增加相关字段:

# sitemapsitemap:  path: sitemap.xml# page urlurl: https://nojsja.github.io/blogs 

之后运行 hexo generate 之后即可以自动生成网站地图 sitemap.xml 了,接下来需要到 Google Search Console 记录自己的站点并提交相应的站点文件。

➣ 向 Google Search Console 提交个人网站地图

  • 登录 Google Search Console
  • 增加自己的网站信息


    Search Console
  • 可以通过几种方法验证所有权


    Search Console
  • 提交插件生成的 sitemap.xml
    sitemap.xml

Google Search Console 还能看到自己网站的点击情况、关键词索引情况等,非常方便。

参考


  1. Lighthouse与Google的手机端最佳实践
  2. Google web.dev

结语


学习前台性能优化的方方面面,一方面是对我们核心基础知识的考察,另一方面也能为我们遇到的少量实际问题提供解决思路,是每个前台人进阶的的必经之路。

以上就是本篇文章的所有内容,后续有需要还会继续升级…

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】2FA验证器 验证码如何登录(2024-04-01 20:18)
【系统环境|】怎么做才能建设好外贸网站?(2023-12-20 10:05)
【系统环境|数据库】 潮玩宇宙游戏道具收集方法(2023-12-12 16:13)
【系统环境|】遥遥领先!青否数字人直播系统5.0发布,支持真人接管实时驱动!(2023-10-12 17:31)
【系统环境|服务器应用】克隆自己的数字人形象需要几步?(2023-09-20 17:13)
【系统环境|】Tiktok登录教程(2023-02-13 14:17)
【系统环境|】ZORRO佐罗软件安装教程及一键新机使用方法详细简介(2023-02-10 21:56)
【系统环境|】阿里云 centos 云盘扩容命令(2023-01-10 16:35)
【系统环境|】补单系统搭建补单源码搭建(2022-05-18 11:35)
【系统环境|服务器应用】高端显卡再度登上热搜,竟然是因为“断崖式”的降价(2022-04-12 19:47)
手机二维码手机访问领取大礼包
返回顶部