目录
1) JPEG/JPG (有损压缩、体积小、加载快、不支持透明)
网站的性能是后端工程师的问题,与前端并无多大关系。
事实上,只有10%~20%的最终用户响应时间是在从Web服务器获取HTML文档并传送到浏览器的,其余80%~90%时间花在了下载页面中的所有组件上。
WEB前端性能优化,提高页面加载速度
一、图片
1.不同业务场景下的图片方案选型
1) JPEG/JPG (有损压缩、体积小、加载快、不支持透明)
优点:有损压缩,图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。
缺点:不支持透明度处理,透明图片需要召唤 PNG 来呈现。
使用场景:大的背景图、轮播图或 Banner 图出现。
2)PNG (无损压缩、质量高、体积大、支持透明)
优点:无损压缩的高保真的图片格式,对线条的处理更加细腻。
缺点: 体积太大。
使用场景: 主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景等。
3)SVG (文本文件、体积小、不失真、兼容性好)
优点:图片可无限放大而不失真。
缺点:渲染成本比较高,以及其它图片格式所没有的学习成本。
使用场景:SVG写入HTML(使用svg标签), SVG写入独立文件后引入HTML(使用img引入)。
4)Base64 (文本文件、依赖编码、小图标解决方案)
优点:减少加载网页图片时对服务器的请求次数,从而提升网页性能(Base64 是作为雪碧图的补充而存在的)。
缺点:Base64 编码后,图片大小会膨胀为原文件的 4/3。
使用场景:图片的实际尺寸很小、图片无法以雪碧图的形式与其它小图结合、图片的更新频率非常低
5)Webp
优点:支持透明、可以显示动态图片。
缺点:太年轻,兼容性不强。
2.CSS sprite优化
`CSS Sprites`直译过来就是CSS精灵,但是这种翻译显然是不够的,其实就是通过将多个图片融合到一副图里面,然后通过CSS的一些技术布局到网页上。
特别是图片特别多的网站,如果能用css sprites降低图片数量,带来的将是速度的提升。
**注意:使用CSS Sprites还有可能降低下载量,可能大家会认为合并后的图片会比分离图片的总和要大,因为还有可能会附加空白区域。
实际上,合并后的图片会比分离的图片总和要小,因为它降低了图片自身的开销,譬如颜色表、格式信息等。**
3.字体图标
在可以大量使用字体图标的地方我们可以尽可能使用字体图标,字体图标可以减少很多图片的使用,从而减少http请求,字体图标还可以通过CSS来设置颜色、大小等样式
4.合并脚本 和样式表
将多个样式表或者脚本文件合并到一个文件中,可以减少HTTP请求的数量从而缩短效应时间。
二、将样式表放在头部
将样式表放在头部对于实际页面加载的时间并不能造成太大影响,但是这会减少页面首屏出现的时间,使页面内容逐步呈现,改善用户体验,防止“白屏”。
将样式表放在文档底部会阻止浏览器中的内容逐步出现。为了避免当样式变化时重绘页面元素,浏览器会阻塞内容逐步呈现,造成“白屏”。这源自浏览器的行为:如果样式表仍在加载,构建呈现树就是一种浪费,因为所有样式表加载解析完毕之前务虚会之任何东西
三、将脚本放在底部
跟样式表相同,脚本放在底部对于实际页面加载的时间并不能造成太大影响,但是这会减少页面首屏出现的时间,使页面内容逐步呈现。
js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。
下载脚本时并行下载是被禁用的——即使使用了不同的主机名,也不会启用其他的下载。因为脚本可能修改页面内容,因此浏览器会等待;另外,也是为了保证脚本能够按照正确的顺序执行,因为后面的脚本可能与前面的脚本存在依赖关系,不按照顺序执行可能会产生错误。
四、CSS选择器优化
1.选择器的类型
ID选择器:#id
类选择器:#class
标签选择器:a
兄弟选择器:#id + a
子代选择器:#id>a
后代选择器:#id a
通配符选择器:*
属性选择器:input[type='input']
伪类和伪元素选择器:a:hover, div:after
组合选择器:#id,.class
2.浏览器的匹配规则
浏览器解析css选择器的规则是从右向左的,好处是为了尽早过滤掉一些无关的样式规则和元素,这样会提高查找选择器所对应的元素的效率。
避免通配符规则:除了*之外,还包括子选择器、后代选择器等。例如 li *,浏览器会查找页面的所有元素,然后一层一层地寻找他的祖先,看是不是li,这就大大的损耗性能,这也就是很多优化原则提到的尽量避免在选择器末尾添加通配符的原因。
不限定ID选择器:ID就是唯一的,不要携程div#nav这样,没必要。
不限定class选择器:可以进一步细化类名,比如li.nav写成nav-item
尽量避免后代选择器:通常后代选择器是开销最高的,如果可以,请使用子代选择器
替换子代选择器:如果可以,用类选择器代替子代选择器,例如nav>li改成.nav-item
依靠继承:了解哪些属性可以依靠继承得来,从而避免重复设定规则
3.关键选择符
选择器中最右边的选择符成为关键选择符,它对浏览器执行的工作量起主要影响。
例如:
div div li span.class-special
各种后代选择器组合,性能低,但是浏览器从右向左匹配,如果页面中
span.class-special
的元素只有一个的话,那影响并不大。反过来看span.class-special li div div
,尽管span.class-special
很少,但是浏览器从右边匹配,查找页面中所有div在层层向上查找,那性能自然就低了。
4.避免CSS表达式
CSS表达式是动态设置CSS属性的一种强大并且危险的方式,它受到了IE5以及之后版本、IE8之前版本的支持。
p {
width: expression(func(),document.body.clientWidth > 400 ? "400px" : "auto");
height: 80px;
border: 1px solid #f00;
}
<script>
var n = 0;
function func() {
n++;
// alert();
console.log(n);
}
</script>
5.重绘和回流
优化CSS选择器不仅仅提高页面加载时候的效率,在页面回流、重绘的时候也可以得到不错的效果
5.1 浏览器的渲染过程
我们可能都知道浏览器含有一个渲染引擎,用来渲染窗口所展示的内容。默认情况下,渲染引擎可以显示html、xml文档以及图片,它也可以借助插件(一种浏览器扩展)显示其他类型的数据,例如用PDF阅读器插件,用于显示PDF格式。
Firefox使用Geoko——Mozilla自主研发的渲染引擎,Safari和Chrome都是用webkit
渲染主流程
渲染引擎首先通过网络获得所请求文档的内容,通常以8k分块的方式完成。
下面是渲染引擎在取得内容之后的基本流程:
解析html以构建dom树—>构建render树—>布局render树—>绘制render树
- DOM Tree:浏览器将HTML解析成树形的数据结构
- CSS Rule Tree:浏览器将CSS解析成树形的数据结构
- Render Tree:DOM和CSSOM合并后生成Render Tree
- Layout:有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置
- Painting:按照算出来的规则,通过显卡,把内容画到屏幕上
Webkit的主要流程
5.2 重绘与回流
-
回流(
reflow
)当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫
reflow
。reflow
会从<html>
这个root frame
开始递归往下,依次计算所有的结点几何尺寸和位置。reflow
几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实际上是元素的显示与隐藏)等,都将引起浏览器的reflow
。鼠标滑过、点击......只要这些行为引起了页面上某些位置的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染。
通常我们都无法预估浏览器到底会
reflow
哪一部分的代码,它们都彼此互相影响着。 -
重绘
改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
注意:回流必将引起重绘,而重绘不一定会引起回流。
5.3 回流何时发生
当页面布局和几何属性改变时就需要回流。下述情况会发生浏览器回流:
添加或者删除可见的DOM元素
元素位置改变
元素的尺寸改变——边距、填充、边框、宽度和高度
内容的改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变
页面渲染初始化
浏览器窗口尺寸改变——resize事件发生时
5.4 如何影响性能
页面上任何一个结点触发reflow,都会导致它的子结点及祖先结点重新渲染。
每次重绘和回流发生时,浏览器会根据对应的css重新绘制需要渲染的部分,如果你的选择器不优化,就会导致效率降低,所以优化选择器的重要性可见一斑。
1.将多次修改保存起来,一次性修改
// 优化前
const el = document.getElementById('el');
for (let i = 0; i < 10; i++) {
el.style.top = el.offsetTop + 10 + "px";
el.style.left = el.offsetLeft + 10 + "px";
}
// 优化后
const el = document.getElementById('el');
let offLeft = el.offsetLeft, offTop = el.offsetTop;
for(let i=0;i<10;i++) {
offLeft += 10;
offTop += 10;
}
el.style.left = offLeft + "px";
el.style.top = offTop + "px";
2.避免逐条改变样式,使用类名去合并样式
// 优化前
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
// 优化后
const container = document.getElementById('container')
container.classList.add('basic_style')
3.将DOM离线
一旦我们给元素设置 `display: none`,将其从页面上“拿掉”,那么我们的后续操作,将无法触发回流与重绘——这个将元素“拿掉”的操作,就叫做 DOM 离线化。
// 优化前
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
// 优化后
let container = document.getElementById('container')
container.style.display = 'none'
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
container.style.display = 'block'