xiaoyu's blog

Coding for a better life!

0%

一次网站性能优化经历

六月份开始搭建的自己的第一个网站:书虫小说

在经历前端页面重写后,网站首屏加载速度有了一定提升,之后发现小说目录详情页白屏时间竟然高达1-2s,拖了一段时间,打算解决这个问题,我用Chrome调试抓包发现json数据包大小竟然有200多KB,测试了一下加载时间大概在700ms-1500ms,由于章节列表的生成依赖于这个数据,所以自然会产生很长一段时间的白屏,我打算先解决这个问题

server传数据是支持gzip压缩的,例如nginx发送html文档给客户端,会自动开启gzip压缩。至于一些其他格式的数据比如json需要手动开启,修改api网站nginx配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gzip             on;

# 设置最小压缩大小,防止出现越压越大的情况
gzip_min_length 1k;

# 代表压缩级别,1-9可选,9最高同时也最耗cpu(但是好像没关系目测了下我CPU很佛系,平时占用超低)
gzip_comp_level 9;

gzip_proxied expired no-cache no-store private auth;

# 代表要压缩的数据类型
gzip_types text/plain application/x-javascript text/css

application/xml application/javascript application/json;

经过这个设置后,json数据从200+KB降到了30+KB,加载时间从原来的700+ms
降到了200+ms, 白屏时间至少减少了1s,可是依然存在。

这时我想到我要加载的是一个长列表,很多小说的章节数高达1000+,有一部《校花的贴身高手》更是高达8000+章,要一次渲染一个这么长的列表是相当耗费性能的,所以决定采用懒加载方案。

方案详情:

1. 数据还是一次性请求,因为数据延迟时间已经可以接受了,分多次请求的反而还增加了服务器压力(重新请求又要建立TCP连接,又要进行三次握手,blablabla...)。
2. 初次载入时只加载部分章节,直到用户滑到网页文档底部时开始插入数据,由于是加载已经缓存在内存中的数据,所以几乎不会有延迟,只是会因重新插入节点页面回流。

如何判断一个元素是否滚动到底?

1
2
var element = document.documentElement //这里拿document来判断
element.scrollHeight - element.scrollTop === element.clientHeight

几个属性及其含义:

  • scrollHeight

表示的是元素的内容高度,包括了视区不可见的内容

scrollHeight

  • scrollTop

一个元素的 scrollTop 值是这个元素的顶部到视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为0。

如图:

scrollTop

  • clientHeight

元素在出现在视区内的高度

有了这些概念,所以当一个滚动元素滑倒底部时,那么element.scrollHeight - element.scrollTop 应该等于 element.clientHeight

我的做法是给documentElement添加了scroll事件处理器,监听滚动事件,由于频繁获取以上这几个属性又挺消耗性能,我给回调函数设置了节流

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
scrollWatcher() { // method
throttle(() => {
let el = document.documentElement;
//这里再减300是为了预先加载,如果等到真的滑倒底部再加载时机有点晚
let flag = el.scrollHeight - el.scrollTop - 300 <= el.clientHeight;
if (flag) {
// console.log(flag)
this.offset += 1;
}
}, 400)();
}

chapters() { // computed

//this.getCatalog.chapters存放的是全部的数据,
//每一次快要滑倒底部事件的触发,就让offset + 1,
//利用数组的切片方法不断更新这个区间,直到全部拿完,
//也就是所有章节在页面加载完毕,移除这个事件监听器

const chapters = this.getCatalog.chapters.slice(0, this.offset * 60 + 60);
if (this.chaptersLen === chapters.length) {
document.removeEventListener('scroll', this.scrollWatcher);
}
return chapters;
}