ShiningDan的博客

博客性能优化之静态资源优化

在本博客中,对 JavaScript 代码和 CSS 代码进行了压缩,并且将压缩后的代码通过内联的信息放在了 HTML 中,用来节省多次 TCP 请求的时间。除了测试 JS、CSS 代码的压缩合并对博客的优化提升,还测试的使用 Base64 处理图片,以及对文章页的图片懒加载实现与优化。

JS 静态资源优化

博客使用 gulp-uglify 来压缩 JS 代码,首先安装:

1
2
3
4
npm install --save-dev gulp-uglify

// 如果使用了 ES6 的语法,则 gulp-uglify 编译会出错,此时先使用 babel 进行编译,再 uglify
npm install --save-dev gulp-babel babel-preset-es2015

然后创建 script 任务,并且添加到任务流中:

1
2
3
4
5
6
7
8
gulp.task('script', function() {
return gulp.src('./view/output/js/*.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(uglify())
.pipe(gulp.dest('./www/static/js'));
})

最后,原 JS 文件通过编译以后生成新的 JS 文件在 ./www/static/js 中。

CSS 静态资源优化

集成并压缩 CSS 文件

针对前一篇博客中提到的过多的 CSS 请求,会导致多次简历 TCP 连接,所以对网页的首屏渲染造成延迟的影响,所以我们要做的是,将不同的 CSS 文件合并成一个 CSS 文件,并且以内联的形式添加到 HTML 页面中。

我的博客自动化框架使用的是 Gulp,所以针对 Gulp,使用 PostCSS 框架,来进行 CSS 的自动添加前缀,CSS 文件的合并,CSS 文件的压缩和导出。

首先安装需要的插件:

1
2
3
4
5
npm install --save-deve gulp-postcss
npm install --save-deve autoprefixer
npm install --save-deve postcss-import
npm install --save-deve css-mqpacker
npm install --save-deve cssnano

并且在 gulpfile.js 中导入模块,创建 css 任务:

1
2
3
4
5
6
7
8
9
10
11
12
let postcss = require('gulp-postcss');
let autoprefixer = require('autoprefixer');
let atImport = require('postcss-import');
let mqpacker = require('css-mqpacker');
let cssnano = require('cssnano');

gulp.task('css', function () {
var processors = [autoprefixer, atImport, mqpacker, cssnano];
return gulp.src('./view/output/*-combo.css')
.pipe(postcss(processors))
.pipe(gulp.dest('./www/static/css'));
});

根据我们的 css 任务的定义,我们要在 /view/output/ 中创建结尾为 -combo.css 的 CSS 文件,用来表示合成的 CSS 文件,我们以 article-combo.css 文件为例,其中的内容为:

1
2
3
4
5
@import '../layout/layout.css';
@import '../header/header.css';
@import '../page-nav/page-nav.css';
@import '../footer/footer.css';
@import '../article/article.css';

里面的内容很简单,就是使用 @import 将相关的 CSS 文件引入,然后 postcss-import 就可以根据 @import 进来的 CSS 文件路径来合成多个 CSS 文件。

除此以外,还需要创建的 CSS 文件有:

1
2
3
4
5
6
7
admin-login-combo.css
archives-combo.css
article-combo.css
home-combo.css
list-combo.css
series-combo.css
upload-combo.css

此时,可以删除 Pug 文件中原来的 CSS 文件,然后使用 include 来引入集成好的 CSS 文件。

最后删除原来 Pug 模板中的 CSS 文件,引入新的集成好的 CSS 文件。

我们可以对比一下 CSS 文件集成前后的区别:

首页

因为首页传输的图片数量比较小,所以可以在 Chrome Network 里面设置网络的情况为:Regular 2G (250kb/s),并且关闭所有的缓存。当 CSS 文件没有被集成之前:

Load 事件被触发的时候,差不多用了 1s 的时间。

当我们设置使用集成的 CSS 文件以后,首页加载的情况如下:

从上图中,我们可以看到,网页中已经没有多余的 CSS 文件来影响首屏渲染速度,当 HTML 加载完成后,也就代表着 CSS 文件加载完成,此时的速度差不多为 650ms,几乎是前面加载多个 CSS 文件的一半!

所以,由此我们可以看出,将多个 CSS 文件合并成一个 CSS 文件,并且通过内联模式引入到 HTML 页面中,可以节省很大一部分时间。

图片优化

在之间的前端性能测试中,图像的加载耗费了很大的时间,在首页上,涉及到的图片有 avatar.jpgheader-bg.jpg,这两张图片的大小都是 303KB,在加载的时候会消耗太长的时间,首先,我们先对这两张图片的大小进行修改。

我们将着两张图片都进行压缩,其中,对 avatar.jpg 压缩成 PNG 格式,并且将大小修改成 800px x 800px,使用锐利压缩的方法,压缩后,avatar.png 的大小为 65KBheader-bg.jpg 的大小为 37KB

下面我们进行测试:

首页

在使用原有的图像的时候,我们看到,这两张图片的加载时间有 3.58s。对网页的加载速度影响很大。

当我们使用新的压缩后的图片,测试的结果如下:

我们可以看到,现在图片加载的时间大约 170ms,基本上没有图片慢慢加载出现的情况了。并且,为了让背景图片加载的时候,没有从白色到有色的“闪烁”情况出现,我们先对背景设定了一个相近的颜色,来缓冲图片加载时的效果:

1
background-color: #DAF0FE; background-image: url('/img/header-bg.jpg');

文章页

因为文章页中包含了很多图片,所以对文章页的图片也需要进行压缩优化。

我们将图片的源文件存储在 /view/output/img 下,压缩以后的图片存储在 /www/static/img 下面。

我们将使用 gulp-imagemin 来实现对图片的自动压缩。首先安装 gulp-imagemin

1
npm install --save-dev gulp-imagemin

然后在 gulpfile.js 中添加自动压缩图片的任务:

1
2
3
4
5
6
7
8
9
10
gulp.task('imagemin', function () {
gulp.src('./view/output/img/*/*/*.{png,jpg,gif,ico}')
.pipe(imagemin({
optimizationLevel: 5, //类型:Number 默认:3 取值范围:0-7(优化等级)
progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
interlaced: true, //类型:Boolean 默认:false 隔行扫描gif进行渲染
multipass: true, //类型:Boolean 默认:false 多次优化svg直到完全优化
}))
.pipe(gulp.dest('./www/static/img'));
});

最后运行 gulp,我们就可以在 /www/static/img/ 下面得到新的被压缩后的图片。

由于我的博客的图片文件大多数都是 PNG 文件,所以我主要对比 PNG 文件的压缩效果。在当前的设置下,一般的 PNG 文件大多数的压缩率有 50% 左右,并且在显示上看不出太多的区别。

除了使用 gulp-imagemin 直接压缩图片以外,针对 PNG 文件,还可以使用 imagemin-pngquant 来进行压缩,首先我们需要安装 imagemin-pngquant

1
npm install --save-dev imagemin-pngquant

然后修改我们的 gulpfile.js

1
2
3
4
5
6
7
8
9
10
11
gulp.task('imagemin', function () {
gulp.src('./view/output/img/**/**/*.{png,jpg,gif,ico}')
.pipe(imagemin({
optimizationLevel: 5, //类型:Number 默认:3 取值范围:0-7(优化等级)
progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
interlaced: true, //类型:Boolean 默认:false 隔行扫描gif进行渲染
multipass: true, //类型:Boolean 默认:false 多次优化svg直到完全优化
use: [pngquant()] //使用pngquant深度压缩png图片的imagemin插件
}))
.pipe(gulp.dest('./www/static/img'));
});

由于 imagemin 不用每次构建都使用,一般只需要在发布或测试的时候一次性压缩就可以了。所以我将 imagemin 任务添加到 browser-sync 任务之前:

1
2
3
gulp.task('default', ['imagemin', 'browser-sync'], function() {
...
}

另外,使用 webp 格式,通常可以让图片文件大小大幅减小,所以,我也希望在自己的博客中添加对 webp 格式的支持。首先安装 gulp-webp

1
npm install --save-dev gulp-webp

然后定义 webp 任务,并且将该任务添加到 gulp 流程中。

1
2
3
4
5
6
7
8
9
gulp.task('webp', function () {
return gulp.src('./view/output/img/**/**/*.{png,jpg}')
.pipe(webp())
.pipe(gulp.dest('./www/static/img'));
});

gulp.task('default', ['imagemin', 'webp', 'browser-sync'], function() {
...
}

我们来看一下压缩的效果:

  • 原图大小:145 KB
  • 压缩后的 PNG 大小:70 KB
  • 压缩后的 WebP 大小:10 KB

可以看到,压缩的效果是显著的。为什么我们有了 WebP 格式的图片后还需要使用 PNG 的图片呢?因为 WebP 并没有得到所有的浏览器的支持,所以我们还需要通过判断浏览器是否支持 WebP 来决定使用什么格式的图片。

检测 WebP 的支持

判断浏览器是否支持 webp 图片,可以检查请求头中的 Accept 字段是否包含 image/webp

如果包含 image/webp 字段,则选择图片格式为 webp 的文章内容发送给浏览器,如果不支持,则选择发送带有 png 格式图片的内容给浏览器。

下面我们可以测试一下:

使用不支持 webp 的 Safari 浏览器时,返回的 img(src) 的类型是 .png

使用支持 webp 的 Chrome 浏览器时,返回的 img(src) 的类型是 .webp

图片懒加载

图片懒加载就是将图片原有的 src 属性中的数据删除,放在一个自定义的属性中(本文使用的是 data-src),当浏览器的显示部分加载到对应的位置时,才下载图片。

为了实现图片懒加载,首先在处理 HTML <img> 标签的时候,先把图片数据中 src 的部分提出来放在 data-src 中,本文使用的是正则表达式在上传 Markdown 文章,进行解析的时候用正则表达式进行处理。

1
2
3
4
let reg = /<img([\s\S]+?)src\s*="([\s\S]+?).(png)"/g;

uploadObj.content = content.replace(reg, '<img$1data-src="$2.png"');
uploadObj.contentWebp = content.replace(reg, '<img$1data-src="$2.webp"');

由于没有图片的时候,图片的位置是被压缩的,所以在加载图片的时候,会突然出现一次页面的下移,对页面的显示效果影响不好,所以,即使在没有图片的时候,我们也需要对图片设置 widthheight,在页面中占据位置。

为了选择合适的 widthheight,我们在 Node.js 解析 Markdown 的时候就获取了图片的宽高,填写在 <img> 标签中。

1
2
3
4
5
6
let reg = /<img([\s\S]+?)src\s*="([\s\S]+?).(png)"/g;
uploadObj.content = content.replace(reg, function(value, p1, p2) {
let imageSize = sizeOf(path.join(__dirname, '../../www/static', p2+'.png'));
return '<img' + p1 + 'data-src="' + p2 +'.png" width=' + imageSize.width + ' height='+imageSize.height;
})
uploadObj.contentWebp = uploadObj.content.replace(reg, '<img$1src="$2.webp"');

我们使用了正则表达式来对 <img> 标签和 src 属性来进行匹配,使用了 image-size 库来获得图片的尺寸,并且将该尺寸添加到 widthheight 属性中。

使用 Chrome 浏览器获得的 HTML 中 <img> 标签的内容如下所示:

使用 Safari 浏览器获得的 HTML 中 <img> 标签的内容如下所示:

最后,我们需要在 HTML 结构都加载完成以后,也就是 DOMContentLoaded 事件触发以后实现懒加载的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
window.addEventListener("DOMContentLoaded", function(event) {
let images = document.getElementsByTagName('img');
let lazyloadDOM = Array.from(images);
let onscrollF = function(event) {
let scrollTop = window.scrollY;
let innerHeight = window.innerHeight;
let scrollBottomHeight = scrollTop + innerHeight;
for (let i = 0; i < lazyloadDOM.length; i++) {
let dom = lazyloadDOM[i];
if (dom.offsetTop < scrollBottomHeight + 300) {
let src = dom.getAttribute('data-src');
dom.setAttribute('src', src);
lazyloadDOM.splice(i, 1);
i = 0;
if (lazyloadDOM.length === 0) {
document.removeEventListener('scroll', onscrollF);
}
} else {
break;
}
}
}
document.addEventListener('scroll', onscrollF)
onscrollF();
})

然后就可以测试啦:

测试效果可见,当网页的还没有刷新到图片的区域时,图片就不会加载,节省了网络的传输资源和加载速度。

首页图片 Base64 处理

首页图片主要涉及两张图片:avatar.pngheader-bg.jpg,其大小分别是:36.5KB63.5KB。下面我们将对着两张图片进行 Base64 编码,并且添加到 CSS 中。

测试的网络环境是 Good 3G (40ms, 1.5Mb/s, 750kb/s)

首先,我们记录一下,不使用 Base64 编码的首页加载速度:

然后使用 Chrome Performance 查看首页渲染速度

从以上的图中我们可以统计如下:

1
2
出现文字,无图片:     300ms 左右
出现文字以及图片: 800ms 左右

下面,我们将图片使用 Base64 编码加载,使用的库是 gulp-base64

首先,我们先给两张图片都添加了 Base64 编码,然后使用 Performance 进行测试,测试的结果如下:

1
出现文字及图片:      830ms 左右

将图片作为 Data URI 添加到 HTML 以后,HTML 的大小变得非常大,大小大于原来的 HTML 加上两张图片的大小之和。并且我们从 Performance 上看到,只有在所有的 HTML 加载完以后,浏览器才开始渲染,所以过大的 Data URI 影响了浏览器的关键渲染路径的速度。

所以,我们得到了以下的经验总结:

  1. 一般使用 Base64 处理的图片都比较小,并且整个网站的复用程度高,不然会影响页面的首屏渲染速度。
  2. Base64 处理的场景,大多是页面只有单个小图片,多个小图片其实可以用雪碧图来进行处理
  3. 针对添加了过大的 Base64 的 CSS 样式,可以用 localstorage 存储在用户的本地,但是其适用场景是该样式不经常发生变动和更新

所以用 Base64 来处理图片添加在 CSS 样式上这种方法,不适合本博客的优化场景。不过,可以在后续的优化中,将 header 中包括 Base64 处理后的 CSS 代码缓存在 localstorage 里面,用来节约博客加载的数据量。