首先我们要明确的问题就是,一个网站除了样式好看,功能简单明了等限制用户浏览量的因素之外,还有什么原因是限制你的网站流量的原因。
渲染性能
在网站资源加载时,大多数用户关心的并不是网站如何加载,而是网站的加载速度,如果一个网站10秒,20秒,甚至30秒都无法加载成功,我相信没有那个用户会继续停留在这个网页上,所以我们必须要思考的问题就是,怎样的网站是顺畅的。下面的是google提出的数据模型:
如果想到达上面的性能需求我们可以从优化浏览器的渲染过程入手,首先让我们从浏览器解析一个页面来开始分析
函数的输入事件处理
首先,我们要知道的一个事实就是浏览器是由多个处理进程的:Compositor、Tile Worker、Main。当用户进行输入操作(滚动、点击等),如滚动时,Compositor 进程会接收到这个事件(实际它可以接受任何用户输入事件),如果可以的话,它将不会通知主进程,直接说:滚吧,牛宝宝。于是,页面就滚动了。当然,这其中包含更新层定位以及让 GPU 绘制帧,而主线程处于空闲状态。但是,事情往往并非如此。如果输入事件上绑定了 JS 处理事件的话,Compositor 进程就没办法主动跳过主进程了。
当 JS 处理事件过长时,输入事件的响应会一直处于阻塞状态,直到 JS 处理完成。当响应超过 100ms 时,用户就会感受到延时。所以当处理用户事件时,我们应该做到:
添加或移除一个 DOM 元素、修改元素属性和样式类、应用动画效果等操作,都会引起 DOM 结构的改变,从而导致浏览器需要重新计算每个元素的样式、对页面或其一部分重新布局(多数情况下)。
渲染性能
在网站资源加载时,大多数用户关心的并不是网站如何加载,而是网站的加载速度,如果一个网站10秒,20秒,甚至30秒都无法加载成功,我相信没有那个用户会继续停留在这个网页上,所以我们必须要思考的问题就是,怎样的网站是顺畅的。下面的是google提出的数据模型:
- Respond:0 – 100ms,视窗一般需要在这个时间段响应用户,超过这个时间段,用户就会感觉到延时。
- Animation:0~16ms,屏幕每秒刷新60次,16ms 代表的是每一帧的时间。用户是非常关注动画的,当动画失帧很容易引起用户察觉。所以动画一般要控制在60FPS。
- Idle:最大化主进程的空闲时间,这样可以及时响应用户输入。
- Load:内容需要在1000ms 内加载出来,超过1000ms 会觉得加载缓慢。
如果想到达上面的性能需求我们可以从优化浏览器的渲染过程入手,首先让我们从浏览器解析一个页面来开始分析
- 转化: 浏览器从磁盘或网络读取 HTML 的原始字节,浏览器会将这段原始文件按照相应编码规范进行解码(现在一般为 utf-8)。
- 符号化:根据 W3C 标准转化为对应的符号(一般在尖括号内)。
- DOM 构建:HTML 解析器会解析其中的 tag 标签,生成 token ,遇到 CSS 或 JS 会发送相应请求。HTML 解析时阻塞主进程的,CSS 一般也是阻塞主进程的(媒体查询时例外),也就是说它们在解析过程中是无法做出响应的。而 JS 手动添加 async 后达到异步加载,根据 token 生成相应 DOM 树。
- CSSOM 构建,添加 CSS 样式生成 CSSOM 树。
- 渲染树构建,从 DOM 树的根节点开始,遍历每个可见的节点,给每个可见节点找到相应匹配的 CSSOM 规则,并应用这些规则,连带其内容及计算的样式。
- 样式计算,浏览器会将所有的相对位置转换成绝对位置等一系列的样式计算。
- 布局,浏览器将元素进行定位、布局。
- 绘制,绘制元素样式,颜色、背景、大小、边框等。
- 合成,将各层合成到一起、显示在屏幕上。
函数的输入事件处理
首先,我们要知道的一个事实就是浏览器是由多个处理进程的:Compositor、Tile Worker、Main。当用户进行输入操作(滚动、点击等),如滚动时,Compositor 进程会接收到这个事件(实际它可以接受任何用户输入事件),如果可以的话,它将不会通知主进程,直接说:滚吧,牛宝宝。于是,页面就滚动了。当然,这其中包含更新层定位以及让 GPU 绘制帧,而主线程处于空闲状态。但是,事情往往并非如此。如果输入事件上绑定了 JS 处理事件的话,Compositor 进程就没办法主动跳过主进程了。
当 JS 处理事件过长时,输入事件的响应会一直处于阻塞状态,直到 JS 处理完成。当响应超过 100ms 时,用户就会感受到延时。所以当处理用户事件时,我们应该做到:
- 避免长时间的 JS 执行。
- 避免在处理中改变样式。因为样式改变会引起后面布局、绘制、合成等操作。
- 对用户输入进行消抖。
- 使用 requestAnimationFrame,将 setTimeout 换成 requestAnimationFrame,因为 setTimeout 时间控制可能造成在一帧的中间,目前各浏览器对 requestAnimationFrame 的支持已经比较好了。
- 使用 Web Workers,将复杂计算的 JS 采用 Web Workers 进行处理。
- 减少垃圾回收,垃圾回收是一个容易被忽略的问题,因为垃圾回收的时间是不受控制的,它可能在一个动画的中途,阻塞动画的执行,更理想的情况是在循环中复用对象。
添加或移除一个 DOM 元素、修改元素属性和样式类、应用动画效果等操作,都会引起 DOM 结构的改变,从而导致浏览器需要重新计算每个元素的样式、对页面或其一部分重新布局(多数情况下)。