Pines-Cheng / blog

技术博客
https://pines-cheng.github.io/blog/
546 stars 42 forks source link

前端性能优化——内容加载 #16

Open Pines-Cheng opened 7 years ago

Pines-Cheng commented 7 years ago

与桌面应用不同,网络应用不需要单独的安装过程:只需输入网址,便可启动和运行 - 这是网络的一个关键特色。不过,要做到这一步,我们通常需要获取几十个(有时甚至是几百个)不同的资源,所有这些资源加起来的数据量高达几兆字节,并且必须在短短几百毫秒内汇聚起来,以实现我们想要达到的即时网络体验。

在满足上述要求的前提下实现即时网络体验绝非易事,优化内容效率至关重要的原因就在于此:避免不必要的下载、通过各种压缩技术优化每个资源的传送编码以及尽可能利用缓存来避免多余的下载。

文本编码和大小优化

除了避免不必要的资源下载,在提高网页加载速度上您可以采取的最有效措施就是,通过优化和压缩其余资源来最大限度减小总下载大小。

优化资源

压缩冗余或不必要数据的最佳方法是将其全部消除。我们不能只是删除任意数据,但在某些环境中,我们可能对数据格式及其属性有内容特定了解,往往可以在不影响其实际含义的情况下显著减小负载的大小。

通用压缩程序(例如设计用于压缩任意文本的压缩程序)在压缩以上网页时可能同样可以取得相当不错的效果,但永远别指望它能去除注释、折叠 CSS 规则或者进行大量的其他内容特定优化。正因如此,预处理/源码压缩/环境感知优化才会成为功能如此强大的工具。

注:举个有说服力的例子,JQuery 内容库未压缩开发版本的大小现已接近大约 300KB。而压缩(移除注解等内容)后同一内容库的大小仅为原来的大约 1/3:大约 100KB。

Gzip

Gzip 是一种可以作用于任何字节流的通用压缩程序。它会在后台记忆一些之前看到的内容,并尝试以高效方式查找并替换重复的数据片段。(欲知详情,请参阅浅显易懂的 GZIP 低阶说明。)GZIP 对基于文本的内容的压缩效果最好,在压缩较大文件时往往可实现高达 70-90% 的压缩率,而如果对已经通过替代算法压缩过的资产(例如,大多数图片格式)运行 GZIP,则效果甚微,甚至毫无效果。

内容库 大小 压缩后大小 压缩比率
jquery-1.11.0.js 276 KB 82 KB 70%
jquery-1.11.0.min.js 94 KB 33 KB 65%
angular-1.2.15.js 729 KB 182 KB 75%
bootstrap-3.1.1.css 118 KB 18 KB 85%

上表显示了 GZIP 压缩对几种最流行的 JavaScript 内容库和 CSS 框架可实现的压缩率。压缩率范围为 60% 至 88%,将文件压缩源码后(产生文件名中包含“.min”的文件),再使用 GZIP 进行压缩,可进一步提高压缩率。

HTML5 Boilerplate 项目包含所有最流行服务器的 配置文件样例,其中为每个配置标志和设置都提供了详细的注解。要为您的服务器确定最佳配置,请执行以下操作: 在列表中找到您喜爱的服务器。 查找 GZIP 部分。 * 确认您的服务器配置了推荐的设置。

image

可通过以下这种快速而又简单的方法了解 GZIP 的实用效果:打开 Chrome DevTools,然后检查“Network”面板中的“Size / Content”列:“Size”表示资产的传送大小,“Content”表示资产的未压缩大小。对于上例中的 HTML 资产,GZIP 在传送时节省了 98.8KB。

最后,尽管大多数服务器会在向用户提供这些资产时自动对其进行压缩,但某些 CDN 需要特别注意和手动操作,以确保 GZIP 资产得到提供。务请审核您的网站并确保资产确实 得到压缩

Tips:

图像优化

图像优化既是一门艺术,也是一门科学:说它是一门艺术,是因为单个图像的压缩并不存在明确的最佳方案,说它是一门科学,则是因为有许多发展成熟的方法和算法都能够显著缩减图像的大小。找到图像的最佳设置需要在许多方面进行认真分析:格式能力、编码数据的内容、质量、像素尺寸等。

消除和替换图像

首先要问问自己,要实现所需的效果,是否确实需要图像。好的设计应该简单,而且始终可以提供最佳性能。如果您可以消除图像资源(与 HTML、CSS、JavaScript 以及网页上的其他资产相比,需要的字节数通常更大),这种优化策略就始终是最佳策略。不过,如果使用得当,图像传达的信息也可能胜过千言万语,因此需要由您来找到平衡点。

接下来您应该考虑是否存在某种替代技术,能够以更高效的方式实现所需的效果:

高分辨率屏幕

高 DPI (HiDPI) 屏幕可以产生绚丽的效果,但也有一个明显的折衷之处:图像资产需要更多的细节,才能对更高的设备像素数加以利用。好在矢量图像最适用于这项任务,因为它们在任何分辨率下都能渲染出清晰的效果。为了渲染出更丰富的细节,我们可能会招致更大的处理开销,但基础资产是相同的,并且与分辨率无关。 image

另一方面,因为光栅图像是以像素为单位编码图像数据,所以面临的挑战要大得多。因此,像素数越大,光栅图像的文件大小就越大。

如果我们将物理屏幕的分辨率加倍,总像素数将增加四倍:双倍的水平像素数乘以双倍的垂直像素数。因此,如果是“2x”的屏幕,所需的像素数不只是加倍,而是增加到原来的四倍!

那么,这有何实际意义呢?我们可以通过高分辨率屏幕提供绚丽的图像,这可以作为产品的一大特色。不过,高分辨率屏幕也需要高分辨率图像:尽可能优先使用矢量图像,因为它们与分辨率无关,并且能够始终提供清晰的效果,而如果需要使用光栅图像,请借助 srcset 和 picture 提供并优化每个图像的多个变体。

矢量图像

矢量图像最适用于包含几何形状的图像,矢量图像与缩放和分辨率无关。

所有现代浏览器都支持可缩放矢量图形 (SVG),这种基于 XML 的图片格式适用于二维图形:我们可以将 SVG 标记直接嵌入网页,也可将其作为外部资源嵌入网页。然后,可通过大多数基于矢量的绘图软件创建一个 SVG 文件,或直接在您喜欢的文本编辑器中手动创建。

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.2" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
   x="0px" y="0px" viewBox="0 0 612 792" xml:space="preserve">
<g id="XMLID_1_">
  <g>
    <circle fill="red" stroke="black" stroke-width="2" stroke-miterlimit="10" cx="50" cy="50" r="40"/>
  </g>
</g>
</svg>

上例渲染的是一个具有黑色轮廓和红色背景的简单圆形,并且是从 Adobe Illustrator 导出。您可以看出,它包含大量元数据,例如图层信息、注解和 XML 命名空间,而在浏览器中渲染资产时通常不需要这些数据。因此,通过 svgo 之类的工具将您的 SVG 文件缩小绝对有益。

svgo 能够将上面这个由 Illustrator 生成的 SVG 文件的大小减少 58%,使其从 470 个字节缩小到 199 个字节。而且,由于 SVG 是一种基于 XML 的格式,因此我们还可以应用 GZIP 压缩来减小其传送大小 - 确保将您的服务器配置为对 SVG 资产进行压缩!

光栅图像

光栅图像就是一个 2 维“像素”栅格,例如,100x100 像素的图像是 10,000 个像素的序列,而每个像素又存储有“RGBA”值:(R) 红色通道、(G) 绿色通道、(B) 蓝色通道和 (A) alpha(透明度)通道。

在内部,浏览器为每个通道分配 256 个值(色阶),也就是每个通道 8 位 (2 ^ 8 = 256),每个像素 4 个字节(4 个通道 x 8 位 = 32 位 = 4 个字节)。因此,如果我们知道栅格尺寸,就能轻易计算出文件大小:

尺寸 像素 文件大小
100 x 100 10,000 39 KB
200 x 200 40,000 156 KB
300 x 300 90,000 351 KB
500 x 500 250,000 977 KB
800 x 800 640,000 2500 KB

100x100 像素图像的文件大小只有 39KB,可能似乎不是什么大问题,但对于更大的图像,文件大小会迅速暴增,并使图像资产的下载既速度缓慢又开销巨大。幸运的是,目前为止我们所描述是“未压缩”图片格式。我们可以采取什么措施来减小图片文件的大小呢?

一个简单的策略是将图像的“位深”从每个通道 8 位减少为更小的调色板:每个通道 8 位为每个通道提供 256 个值,共计提供 16777216 (2563) 种颜色。如果我们将调色板减少为 256 色,会出现什么情况?那样的话,RGB 通道一共只需要 8 位,每个像素立即可以节约两个字节,与原来每个像素 4 个字节的格式相比,通过压缩节约了 50% 的字节!

image

注:从左至右 (PNG):32 位(16M 色)、7 位(128 色)、5 位(32 色)。包含渐变色过渡的复杂场景(渐变、天空等)需要较大的调色板,以避免在 5 位资产中产生马赛克天空之类的视觉伪影。另一方面,如果图像只使用几种颜色,较大的调色板只会浪费宝贵的位数!

接下来,在优化了各个像素中存储的数据之后,我们可以多动动脑筋,看看能不能对相邻像素也做做优化:其实,许多图像(尤其是照片)的大量相邻像素都具有相似的颜色 - 例如,天空、重复的纹理等。压缩程序可以利用这些信息采用“增量编码”,在这种编码方式下,并不为每个像素单独存储值,而是存储相邻像素之间的差异:如果相邻像素相同,则增量为“零”,我们只需存储一位!但是这显然还不够...

人眼对不同颜色的敏感度是不同的:为此,我们可以通过减小或增大这些颜色的调色板来优化颜色编码。“相邻”像素构成二维栅格,这意味着每个像素都有多个相邻像素:我们可以利用这一点进一步改进增量编码。我们不再只是关注每个像素直接相邻的像素,而是着眼于更大块的相邻像素,并使用不同设置对不同的像素块进行编码。当然还不止这些...

您可以看出,图像优化很快就会复杂(或者有趣起来,全凭您怎么看了),这也是学术和商业研究都很活跃的一个领域。由于图像占据了大量字节,因此开发更好的图像压缩方法具有极大价值!如果您很想了解更多信息,请访问 Wikipedia 网页,或查看 WebP 压缩方法白皮书中 提供的实例。

无损图像压缩与有损图像压缩

人眼对不同颜色的敏感度不同,这意味着我们可以使用较少的位数来编码某些颜色。因此,典型的图像优化过程由两个高级步骤组成:

第一步是可选步骤,具体算法将取决于特定的图片格式,但一定要了解,任何图像都可通过有损压缩步骤来减小其大小。 实际上,不同图片格式(例如 GIF、PNG、JPEG 以及其他格式)之间的差异在于它们在执行有损和无损压缩步骤时所使用(或省略)特定算法的组合。

选择正确的图片格式

除了不同的有损和无损压缩算法外,不同的图片格式还支持不同的功能,例如动画和透明度 (alpha) 通道。因此,需要将所需视觉效果与功能要求相结合来为特定图像选择“正确的格式”。

格式 透明度 动画 浏览器
GIF 支持 支持 所有
PNG 支持 不支持 所有
JPEG 不支持 不支持 所有
JPEG XR 支持 支持 IE
WebP 支持 支持 Chrome、Opera、Android

获得普遍支持的图片格式有三种:GIF、PNG 和 JPEG。除了这些格式外,一些浏览器还支持较新的格式(例如 WebP 和 JPEG XR),它们的总体压缩率更高,提供的功能也更多。那么,您应该使用哪一种格式呢?

image

最后,为每一项资产确定了最佳图片格式及其设置之后,请考虑增加一个以 WebP 和 JPEG XR 格式编码的变体。这两种格式均为新格式,并且遗憾的是,它们没有(尚未)得到所有浏览器的普遍支持,但尽管如此,它们仍可为较新的客户端显著降低文件大小,例如,平均来说,与可比的 JPEG 图像相比,WebP 可将文件大小减小 30%

由于 WebP 和 JPEG XR 均未得到普遍支持,您需要向应用或服务器添加额外的逻辑来提供相应的资源:

最后请注意,如果您使用 Webview 在本机应用中渲染内容,就可以完全控制客户端,并可独占使用 WebP!Facebook、Google+ 以及许多其他应用都使用 WebP 来提供其应用内的所有图像 - 实现的文件大小缩减定然物有所值。如需了解有关 WebP 的更多信息,请观看 Google I/O 2013 上的演讲 WebP:部署更快速、更小并且更绚丽的图像

提供缩放的图像

图像优化可归结为两个标准:优化编码每个图像像素所使用的字节数,和优化总像素数:图像的文件大小就是总像素数与编码每个像素所使用字节数的乘积。不多不少。

图像优化检查清单

图像优化既是一门艺术,也是一门科学:说它是一门艺术,是因为单个图像的压缩并不存在明确的最佳方案,说它是一门科学,则是因为有一些发展成熟的方法和算法有助于显著缩减图像的大小。

在您努力优化图像时,要记住以下这些技巧和方法:

网页字体优化

网页字体详解

Unicode 字体可能包含数千种字形。字体格式有四种:WOFF2、WOFF、EOT 和 TTF。

目前网络上使用的字体容器格式有四种:EOT、TTF、WOFF 和 WOFF2。遗憾的是,尽管选择范围很广,但仍然缺少在所有新旧浏览器上都能使用的单一通用格式:EOT 只有 IE 支持,TTF 获得了部分 IE 支持,WOFF 获得了最广泛的支持,但在某些较旧的浏览器上不受支持,而 WOFF 2.0 支持对许多浏览器来说尚未实现

我们需要提供多种格式才能实现一致的体验:

注:考虑使用 Zopfli 压缩处理 EOT、TTF 和 WOFF 格式。Zopfli 是一种兼容 zlib 的压缩工具,提供的文件大小压缩率比 gzip 高大约 5%。

网页字体和关键渲染路径

字体延迟加载带有一个可能会延迟文本渲染的重要隐藏影响:浏览器必须构建渲染树(它依赖 DOM 和 CSSOM 树),然后才能知道需要使用哪些字体资源来渲染文本。因此,字体请求的处理将远远滞后于其他关键资源请求的处理,并且在获取资源之前,可能会阻止浏览器渲染文本。

image

  1. 浏览器请求 HTML 文档。
  2. 浏览器开始解析 HTML 响应和构建 DOM。
  3. 浏览器发现 CSS、JS 以及其他资源并分派请求。
  4. 浏览器在收到所有 CSS 内容后构建 CSSOM,然后将其与 DOM 树合并以构建渲染树
    • 在渲染树指示需要哪些字体变体在网页上渲染指定文本后,将分派字体请求。
  5. 浏览器执行布局并将内容绘制到屏幕上。
    • 如果字体尚不可用,浏览器可能不会渲染任何文本像素。
    • 字体可用之后,浏览器将绘制文本像素。

网页内容的首次绘制(可在渲染树构建后不久完成)与字体资源请求之间的“竞赛”产生了“空白文本问题”,出现该问题时,浏览器会在渲染网页布局时遗漏所有文本。

不同浏览器之间实际行为有所差异:

通过 Font Loading API 优化字体渲染

Font Loading API 提供了一种脚本编程接口来定义和操纵 CSS 字体,追踪其下载进度,以及替换其默认延迟下载行为。例如,如果您确定将需要特定字体变体,您可以定义它并指示浏览器启动对字体资源的立即获取:

var font = new FontFace("Awesome Font", "url(/fonts/awesome.woff2)", {
  style: 'normal', unicodeRange:'U+000-5FF', weight:'400'
});

font.load(); // don't wait for the render tree, initiate an immediate fetch!

font.ready().then(function() {
  // apply the font (which may re-render text and cause a page reflow)
  // after the font has finished downloading
  document.fonts.add(font);
  document.body.style.fontFamily = "Awesome Font, serif";

  // OR... by default the content is hidden, 
  // and it's rendered after the font is available
  var content = document.getElementById("content");
  content.style.visibility = "visible";

  // OR... apply your own render strategy here... 
});

并且,由于您可以检查字体状态(通过 check() 方法)并追踪其下载进度,因此您还可以为在网页上渲染文本定义一种自定义策略:

最重要的是,您还可以混用和匹配上述策略来适应网页上的不同内容。例如,在获得字体前延迟某些部分的文本渲染;使用后备字体,然后在字体下载完成后进行重新渲染;指定不同的超时,等等。

注:在某些浏览器上,Font Loading API 仍处于开发阶段。可以考虑使用 FontLoader polyfillwebfontloader 内容库来提供类似功能,尽管附加的 JavaScript 依赖关系会产生开销。

通过内联优化字体渲染

使用 Font Loading API 消除“空白文本问题”的简单替代策略是将字体内容内联到 CSS 样式表内:

内联策略不那么灵活,不允许您为不同的内容定义自定义超时或渲染策略,但不失为是一种适用于所有浏览器并且简单而又可靠的解决方案。为获得最佳效果,请将内联字体分成独立的样式表,并为它们提供较长的 max-age。这样一来,在您更新 CSS 时,就不会强制访问者重新下载字体。

注:有选择地使用内联。回想一下,@font-face 使用延迟加载行为来避免下载多余的字体变体和子集的原因。此外,通过主动式内联增加 CSS 的大小将对您的关键渲染路径产生不良影响。浏览器必须下载所有 CSS,然后才能构造 CSSOM,构建渲染树,以及将页面内容渲染到屏幕上。

通过HTTP缓存优化字体重复使用

字体资源通常是不会频繁更新的静态资源。因此,它们非常适合较长的 max-age 到期 - 确保您为所有字体资源同时指定了条件 ETag 标头最佳 Cache-Control 策略

您无需在 localStorage 中或通过其他机制存储字体,其中的每一种机制都有各自的性能缺陷。 浏览器的 HTTP 缓存与 Font Loading API 或 webfontloader 内容库相结合,实现了最佳并且最可靠的机制来向浏览器提供字体资源。

优化检查清单

与普遍的观点相反,使用网页字体不需要延迟网页渲染,也不会对其他性能指标产生不良影响。在充分优化的情况下使用字体可大幅提升总体用户体验:出色的品牌推广,改进的可读性、易用性和可搜索性,并一直提供可扩展的多分辨率解决方案,能够出色地适应各种屏幕格式和分辨率。不要害怕使用网页字体!

不过,直接实现可能招致下载内容庞大和不必要的延迟。您需要通过对字体资产本身及其在网页上的获取和使用方式进行优化来为浏览器提供协助的环节。

参考

Pines-Cheng commented 5 years ago

Webpack Code Splitting

Prevent Duplication

The SplitChunksPlugin allows us to extract common dependencies into an existing entry chunk or an entirely new chunk. Let's use this to de-duplicate the lodash dependency from the previous

Here are some other useful plugins and loaders provided by the community for splitting code:

Dynamic Imports

Two similar techniques are supported by webpack when it comes to dynamic code splitting. The first and recommended approach is to use the import() syntax that conforms to the ECMAScript proposal for dynamic imports. The legacy, webpack-specific approach is to use require.ensure. Let's try using the first of these two approaches...

Prefetching/Preloading modules

webpack 4.6.0+ adds support for prefetching and preloading.

Using these inline directives while declaring your imports allows webpack to output “Resource Hint” which tells the browser that for:

Simple prefetch example can be having a HomePage component, which renders a LoginButton component which then on demand loads a LoginModal component after being clicked.

Bundle Analysis

Once you start splitting your code, it can be useful to analyze the output to check where modules have ended up. The official analyze tool is a good place to start. There are some other community-supported options out there as well:

Pines-Cheng commented 5 years ago

Tree Shaking

Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. import and export. The name and concept have been popularized by the ES2015 module bundler rollup.

The webpack 2 release came with built-in support for ES2015 modules (alias harmony modules) as well as unused module export detection. The new webpack 4 release expands on this capability with a way to provide hints to the compiler via the "sideEffects" package.json property to denote which files in your project are "pure" and therefore safe to prune if unused.

Pines-Cheng commented 5 years ago

Measuring Javascript and CSS coverage

Using the coverage tool in Chrome you can check that for any webpage. Just hit cmd + shift + p and type "coverage".

image

image

mage