前言
本篇章来源于https://juejin.cn/post/7077347573740077069,内容只是经过咀嚼便于自己理解。
面试回答
1.重绘重排:简单来说重绘就是改变某个节点的样式,重排就是改变某些节点的布局,比如元素尺寸变动、元素位置变动以及浏览器窗口变动。所以重排一定会引起重绘,而重绘不一定引起重排。减少重绘重排的方式主要是通过集中修改css来完成,比如将样式统一放在class当中,在离线、脱离文档流的DOM元素中进行修改最后一起显示等。
2.性能优化:前端的性能优化有两个优化方向,一种是页面渲染优化,比如添加loading,使用骨架屏,图片懒加载,异步加载css,能一定程度上抛开js和css。还有一种是资源加载优化,比如体积优化,一般js、css文件会普遍偏大,可以拆分js分成多个小js,按需引入插件,压缩图片,使用在线图片等方式。
知识点
1.内联首屏关键CSS
性能优化中有一个重要的指标是首次有效绘制(First Meaningful Paint,简称FMP),即指页面的首要内容(primary content)出现在屏幕上的时间。这一指标影响用户看到页面前所需等待的时间,而内联首屏关键CSS能减少这一时间。
很多人都喜欢通过link
标签引用外部CSS文件
。但需要知道的是,将CSS直接内联到HTML文档中能使CSS更快速地下载。而使用外部CSS文件时,需要在HTML文档下载完成后才知道所要引用的CSS文件,然后才下载它们。所以说,内联CSS能够使浏览器开始页面渲染的时间提前,因为在HTML下载完成之后就能渲染了。
但是我们不应该将所有的CSS都内联在HTML文档中,因为(初始拥塞窗口)存在限制(TCP相关概念,通常是 14.6kB,压缩后大小)
,如果内联CSS后的文件超出了这一限制,系统就需要在服务器和浏览器之间进行更多次的往返,这样并不能提前页面渲染时间。因此,我们应当只将渲染首屏内容所需的关键CSS内联到HTML中。
还有一点需要注意的是内联CSS没有缓存,每次都会随HTML的加载而重新下载,但我们将内联首屏关键CSS控制在 14.6kB
以内,它对性能优化还是起到正向作用的。(凡事有利也有弊)
2.异步加载非首屏css
首先确认两点:
CSS
不会阻塞DOM
的解析,但会阻塞DOM
的渲染CSS
会阻塞JS
执行,但不会阻塞JS
文件的下载
由于CSS会阻塞DOM的渲染,所以我们将首屏关键CSS内联后,剩余的非首屏CSS内容可以使用外部CSS,并且异步加载,防止非首屏CSS内容阻塞页面的渲染。
CSS有四种异步加载方式:
1.第一种方法是动态创建
// 创建link标签
const myCSS = document.createElement( "link" );
myCSS.rel = "stylesheet";
myCSS.href = "mystyles.css";
// 插入到header的最后位置
document.head.insertBefore(myCSS,document.head.childNodes[document.head.childNodes.length-1].nextSibling );
2.第二种方法是将link元素的media属性设置为用户浏览器不匹配的媒体类型(或媒体查询)
对浏览器来说,如果样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的情况下再进行下载。在首屏文件加载完成之后,将media的值设为screen
或all
,从而让浏览器开始解析CSS。
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
3.第三种方法是通过rel属性将link元素标记为alternate可选样式表
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
4.第四种方法是使用rel=preload来异步加载CSS
<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
注意,as
是必须的。忽略as
属性,或者错误的as
属性会使preload
等同于XHR
请求,浏览器不知道加载的是什么内容,因此此类资源加载优先级会非常低。as
的可选值可以参考上述标准文档。
看起来,rel="preload"
的用法和上面两种没什么区别,都是通过更改某些属性,使得浏览器异步加载CSS文件但不解析,直到加载完成并将修改还原,然后开始解析。
但是它们之间其实有一个很重要的不同点,那就是使用preload,比使用不匹配的media方法能够更早地开始加载CSS。所以尽管这一标准的支持度还不完善,仍建议优先使用该方法。
3.简化选择器层级
CSS选择器的匹配是从右向左进行的,这一策略导致了不同种类的选择器之间的性能也存在差异。相比于#markdown-content-h3
,显然使用#markdown .content h3
时,浏览器生成渲染树(render-tree)所要花费的时间更多。因为后者需要先找到DOM中的所有h3
元素,再过滤掉祖先元素不是.content
的,最后过滤掉.content
的祖先不是#markdown
的。试想,如果嵌套的层级更多,页面中的元素更多,那么匹配所要花费的时间代价自然更高。
不过现在浏览器在这一方面做了很多优化,不同选择器的性能差别并不明显,甚至可以说差别甚微。此外不同选择器在不同浏览器中的性能表现也不完全统一,在编写CSS的时候无法兼顾每种浏览器。鉴于这两点原因,我们在使用选择器时,只需要记住以下几点,其他的可以全凭喜好。
- 保持简单,不要使用嵌套过多过于复杂的选择器。
- 通配符和属性选择器效率最低,需要匹配的元素最多,尽量避免使用。
- 不要使用类选择器和ID选择器修饰元素标签,如h3#markdown-content,这样多此一举,还会降低效率。
- 不要为了追求速度而放弃可读性与可维护性。
4.减少使用耗性能的属性
在浏览器绘制屏幕时,所有需要浏览器进行操作或计算的属性相对而言都需要花费更大的代价。当页面发生重绘时,它们会降低浏览器的渲染性能。所以在编写CSS时,我们应该尽量减少使用昂贵属性,如box-shadow
/border-radius
/filter
/opacity
/:nth-child
等。
当然,并不是让大家不要使用这些属性,因为这些应该都是我们经常使用的属性。之所以提这一点,是让大家对此有一个了解。当有两种方案可以选择的时候,可以优先选择没有昂贵属性或昂贵属性更少的方案,如果每次都这样的选择,网站的性能会在不知不觉中得到一定的提升。
5.不使用@import
不建议使用@import主要有以下两点原因:
1.使用@import引入CSS会影响浏览器的并行下载。使用@import引用的CSS文件只有在引用它的那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载,这时才去下载,然后下载后开始解析、构建render tree等一系列操作。这就导致浏览器无法并行下载所需的样式文件。
2.多个@import会导致下载顺序紊乱。在IE中,@import会引发资源文件的下载顺序被打乱,即排列在@import后面的js文件先于@import下载,并且打乱甚至破坏@import自身的并行下载。
所以不要使用这一方法,使用link标签就行了。
6.使用硬件加速: will-change
参考博客:https://developer.aliyun.com/article/973113
will-change属性允许你提前告知浏览器你可能会对一个元素进行什么样的改变,这样它就可以提前设置适当的优化,避免了可能会对页面的响应性产生负面影响的启动成本。这些元素可以更快地被改变和渲染,页面将能够迅速地更新,从而带来更流畅的体验。
例如,当在一个元素上使用CSS三维变换时,该元素及其内容可能会升到一个新的图层,之后才会被合成(绘制到屏幕上)。然而,在一个新的图层中设置元素是一个代价相对昂贵的操作,这可能会使变换动画的开始时间延迟几分之一秒,导致明显的“闪烁”。
为了避免这种延迟,你可以在变化实际发生前的一段时间通知浏览器。这样,浏览器就会有一些时间为这些变化做准备,当这些变化发生时,元素的图层就会准备好,变换动画就可以执行,然后元素就可以被渲染,页面就会迅速更新。
使用will-change,向浏览器提示即将发生的变换,可以在你期望被变换的元素上添加这个简单的规则:
will-change: transform;
你也可以向浏览器声明,你打算改变一个元素的滚动位置(该元素在可见滚动窗口中的位置,以及在该窗口中的可见程度)、或者改变其内容、一个或多个CSS属性值,方法是指定你期望改变的属性的名称。如果你期望或计划改变一个元素的多个项,你可以提供一个逗号分隔的值的列表。
例如,你可以这样向浏览器声明:
will-change: transform, opacity;
指定你想要改变的具体内容,可以让浏览器更好地优化为这些特定的变化。这显然提升速度的一个更好的方法,而不必诉诸于hack以及强迫浏览器进行不必要图层的创建。
除了对浏览器的渲染提示之外,will-change 会影响它声明的元素吗?
会或者不会取决于你所声明并告知浏览器的属性。如果一个属性的任何非初始值都会在该元素上创建一个stacking context ,那么在will-change中指定该属性将在该元素上创建一个层叠上下文。
译者注:如果一个元素含有层叠上下文,我们可以理解为这个元素在z轴上的level更高一级。换句话说就是于网页中元素级别更高,离用户更近了。
例如,clip-path和opacity属性,当它们的值不是初始值时,都会在被应用的元素上创建一个层叠上下文。因此,使用这些属性中的一个(或两个)作为will-change的值会在元素上创建一个层叠上下文,甚至在变化实际发生之前创建。这同样适用于其他会在元素上创建层叠上下文的属性。
另外,一些属性可以导致为固定位置的元素创建一个包含块。例如,一个被转换的元素为其所有定位的子元素创建一个包含块,即使是那些已经被设置为position: fixed的元素。所以,如果一个属性导致了一个包含块的产生,那么把它指定为will-change的值也会导致固定位置元素产生一个包含块。
译者注:一般情况下,CSS中一个元素的位置和尺寸的计算都相对于一个矩形,这个矩形被称为包含块。
如前所述,will-change
的一些变化会导致创建一个新的合成层。然而,GPU并不支持大多数浏览器中CPU所做的子像素抗锯齿,有时会导致内容模糊(尤其是文字)。
除此之外,will-change
属性对它所指定的元素没有直接影响,它只是对浏览器的一个渲染提示,允许它为该元素发生的变化进行优化设置。除了在上述情况下创建堆叠上下文和包含块之外,它对元素没有直接影响。
1.will-change注意事项
知道了will-change
的作用后,你可能会想:“只要让浏览器优化所有的东西就行了!” 这个想法很合理,谁不希望他们所有的变化都被优化,并在需要时准备好?
尽管will-change
很🐂🍺,但它与其他种类的“权力”没有任何区别,滥用它会导致性能上的打击,可能使你的页面崩溃。
与任何优化一样,will-change
也有其不能直接察觉的副作用(毕竟,它只是一种在幕后与浏览器对话的方式),所以它的使用可能很棘手。这里有一些你在使用这个属性时需要注意的事情,以确保你能得到它的最佳效果,同时避免滥用它可能带来的伤害。
2.给浏览器足够的时间
will-change
属性之所以叫做will是因为:通知浏览器即将发生的变化,而不是正在发生的变化。
使用will-change
,我们要求浏览器对我们所声明的变化进行某些优化,为了实现这一目标,浏览器需要一些时间来实际进行这些优化,这样,当变化发生时,优化就可以毫无延迟地应用。
在一个元素发生变化之前立即对其设置will-change
,几乎没有任何效果。(它实际上可能比不设置更糟,可能会有一个新图层的成本,而你正在做的动画以前并没有资格做一个新层!)
例如,如果一个变化将在悬停时发生:
.element:hover {
will-change: transform;
transition: transform 2s;
transform: rotate(30deg) scale(1.5);
}
告诉浏览器为已经发生的变化进行优化是没有用的,这样做是否定了will-change
背后的整个概念。相反,你应该找到一种方法,至少可以稍微提前预测会发生变化的东西,并在那时设置will-change
。
例如,如果一个元素在被点击的时候会发生变化,那么就在该元素被悬停的时候设置will-change,这样就给了浏览器足够的时间来优化。从用户悬停该元素到实际点击该元素之间的时间足以让浏览器进行优化设置,因为人类的反应时间相对较慢,所以在变化实际发生之前,浏览器会有大约200ms的时间窗口,这足以让它进行优化设置。
.element {
transition: transform 1s ease-out;
}
.element:hover {
will-change: transform;
}
.element:active {
transform: rotateY(180deg);
}
但如果你希望变化发生在悬停时,而不是点击时呢?上面的声明将是无用的,在这种情况下,往往还是可以找到一些方法来预测动作发生之前的情况。
例如,悬停变化元素的祖先可能会提供足够的准备时间:
.element {
transition: opacity .3s linear;
}
/* 当鼠标进入或悬停在其祖先时,声明该元素的变化 */
.ancestor:hover .element {
will-change: opacity;
}
/* 当元素被悬停时的变化 */
.element:hover {
opacity: .5;
}
然而,悬停祖先并不总是表明该元素肯定会被交互,所以你可以做一些事情,比如当视图在你的应用程序中变得活跃时,或者如果该元素在视口的可见部分内,设置will-change,这将增加该元素被交互的机会。
3.更改完成后删除
浏览器为即将发生的变化所做的优化通常会占用机器的很多资源,通常要删除这些优化尽快恢复到正常行为。然而,will-change
覆盖了这一行为,它维持优化的时间比浏览器所做的要长很多。
因此,你应该始终记得在元素变化完成后删除will-change
,这样浏览器就可以恢复优化所占用的资源。
如果在样式表中声明了will-change
,就不可能删除它,这就是为什么建议你用JavaScript设置和取消它。通过脚本,你可以向浏览器声明你的修改,然后在修改完成后,通过监听这些修改完成的时间来删除will-change
。
例如,就像我们在上一节的样式规则中所做的那样,你可以监听元素(或其祖先)何时被悬停,然后在鼠标进入时设置will-change。如果你的元素正在被动画化,你可以使用DOM事件animationEnd
来监听动画何时结束,然后在animationEnd
被触发时移除will-change
。
// 一个例子
// 获取点击时将被动画化的元素,例如
var el = document.getElementById('element');
// 设置元素被悬停时的变化
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);
function hintBrowser() {
// 在动画的关键帧中要改变的可优化的属性
this.style.willChange = 'transform, opacity';
}
function removeHint() {
this.style.willChange = 'auto';
}
Craig Buckler写了一篇关于在JavaScript中捕捉CSS动画事件的文章,如果你不熟悉这个,可以去看看。CSS-Tricks上也有一篇关于控制CSS动画和过渡的文章,也值得一看。
4.在样式表中谨慎使用
正如我们在上一节中所看到的,will-change
可以用来提示浏览器在几毫秒内某个元素即将发生的变化。这就是在样式表中声明will-change
的用例之一。尽管我们建议使用JavaScript来设置和取消will-change
,但在某些情况下,在样式表中设置它(并保持它)是合适的。
一个例子:在一些元素上设置will-change
,这些元素可能会被用户反复交互,并且应该以一种快速的方式响应用户的交互。有限的元素数量意味着浏览器所做的优化不会被过度使用,因此不会有太大的伤害。
例如,通过在用户要求时将侧边栏滑出的方式来改造它。下面的规则就很合适:
.sidebar {
will-change: transform;
}
另一个例子:在不断变化的元素上使用will-change
,比如一个响应用户鼠标移动的元素,随着鼠标的移动在屏幕上移动。在这种情况下,只需在样式表中声明will-change
的值就可以了,因为它准确地描述了该元素将定期/不断地变化,所以应该保持优化。
.annoying-element-stuck-to-the-mouse-cursor {
will-change: left, top;
}
5.属性值
will-change
有四个属性值:auto
,scroll-position
,contents
和 <custom-ident>
。
<custom-ident>
值用于指定你期望改变的一个或多个属性的名称。多个属性用逗号隔开。
下面是带有指定属性名称的有效的will-change
声明的例子。
will-change: transform;
will-change: opacity;
will-change: top, left, bottom, right;
复制代码
<custom-ident>
:排除了关键字will-change
、none
、all
、auto
、scroll-position
和contents
,还有一些通常被排除在外的关键字。所以,正如我们在文章开头提到的,will-change: all
的声明是无效的,会被浏览器忽略。
auto
并没有特别的意思,除了通常的优化外,浏览器不会设置任何特别的优化。
scroll-position
:顾名思义,表示你希望在不久的将来随时改变一个元素的滚动位置。这个值很有用,在使用时浏览器会准备并渲染可滚动元素上滚动窗口中可见内容之外的内容。浏览器通常只渲染滚动窗口中的内容,以及超过该窗口的部分内容,以平衡因跳过渲染而节省的内存和时间以及使滚动看起来更漂亮。使用will-change: scroll-position
,它可以做进一步的渲染优化,从而使更长或更快的内容滚动可以顺利地完成。
contents
:预计该元素的内容会发生变化。浏览器通常会随着时间的推移缓存元素的渲染,因为大多数东西并不经常改变,或者只改变它们的位置。这个值会被浏览器解读为一个信号,即减少对该元素的缓存,或者完全避免对该元素的缓存,因为如果该元素的内容经常变化,那么保持对内容的缓存将是无用且浪费时间的,所以浏览器会直接停止缓存,只要该元素的内容发生变化,就继续从头渲染。
7.减少重排重绘
在网站的使用过程中,某些操作会导致样式的改变,这时浏览器需要检测这些改变并重新渲染,其中有些操作所耗费的性能更多。我们都知道,当FPS为60时,用户使用网站时才会感到流畅。这也就是说,我们需要在16.67ms内完成每次渲染相关的所有操作,所以我们要尽量减少耗费更多的操作。
1.重绘与重绘
重排:
重排会导致浏览器重新计算整个文档,重新构建渲染树,这一过程会降低浏览器的渲染速度。如下所示,有很多操作会触发重排,我们应该避免频繁触发这些操作。
- 改变font-size和font-family
- 改变元素的内外边距
- 通过JS改变CSS类
- 通过JS获取DOM元素的位置相关属性(如width/height/left等)
- CSS伪类激活
- 滚动滚动条或者改变窗口大小
值得一提的是,某些CSS属性具有更好的重排性能。如使用flex时,比使用inline-block和float时重排更快,所以在布局时可以优先考虑flex。
重绘:
当元素的外观(如color,background,visibility等属性)发生改变时,会触发重绘。在网站的使用过程中,重绘是无法避免的。不过,浏览器对此做了优化,它会将多次的重排、重绘操作合并为一次执行。不过我们仍需要避免不必要的重绘,如页面滚动时触发的hover事件,可以在滚动的时候禁用hover事件,这样页面在滚动时会更加流畅。
合并对DOM样式的修改,采用css class来修改
const el = document.querySelector('.box')
el.style.margin = '5px'
el.style.borderRadius = '12px'
el.style.boxShadow = '1px 3px 4px #ccc'
--->
.update{
margin: 5px;
border-dadius: 12px;
box-shadow: 1px 3px 4px #ccc
}
const el = document.querySelector('.box')
el.classList.add('update')
如果需要对DOM进行多次访问,尽量使用局部变量缓存该DOM
避免使用table布局,可能很⼩的⼀个⼩改动会造成整个table的重新布局
CSS选择符从右往左匹配查找,避免节点层级过多。
此外,我们编写的CSS中动画相关的代码越来越多,我们已经习惯于使用动画来提升用户体验。我们在编写动画时,也应当参考上述内容,减少重绘重排的触发。除此之外我们还可以通过硬件加速和will-change来提升动画性能。
最后需要注意的是,用户的设备可能并没有想象中的那么好,至少不会有我们的开发机器那么好。我们可以借助Chrome的开发者工具进行CPU降速,然后再进行相关的测试,降速方法如下图所示。
如果需要在移动端访问的,最好将速度限制更低,因为移动端的性能往往更差。
2.DOM离线处理,减少回流重绘次数
离线的DOM不属于当前DOM树中的任何一部分,这也就意味着我们对离线DOM处理就不会引起页面的回流与重绘。
使用display: none,将元素从渲染树中完全移除,元素既不可见,也不是布局的组成部分,之后在该DOM上的操作不会触发回流与重绘,操作完之后再将
display
属性改为显示,只会触发这一次回流与重绘。Tip:
visibility : hidden
的元素只对重绘有影响,不影响重排。- 通过documentFragment创建一个dom文档片段,在它上面批量操作dom,操作完成之后,再添加到文档中,这样只会触发一次重排。
const el = document.querySelector('.box')
const fruits = ['front', 'nanjiu', 'study', 'code'];
const fragment = document.createDocumentFragment();
fruits.forEach(item => {
const li = document.createElement('li');
li.innerHTML = item;
fragment.appendChild(li);
});
el.appendChild(fragment);
- 克隆节点,修改完再替换原始节点
const el = document.querySelector('.box')
const fruits = ['front', 'nanjiu', 'study', 'code'];
const cloneEl = el.cloneNode(true)
fruits.forEach(item => {
const li = document.createElement('li');
li.innerHTML = item;
cloneEl.appendChild(li);
});
el.parentElement.replaceChild(cloneEl,el)
3.DOM脱离普通文档流
使用absoult或fixed让元素脱离普通文档流,使用绝对定位会使的该元素单独成为渲染树中body的一个子元素,重排开销比较小,不会对其它节点造成太多影响。
4.CSS3硬件加速(GPU加速)
使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。
常见的触发硬件加速的css属性:
- transform
- opacity
- filters
- will-change
5.将节点设置为图层
图层能够阻⽌该节点的渲染⾏为影响别的节点。⽐如对于video标签来说,浏览器会⾃动将该节点变为图层。
8.css文件压缩
这应该是最容易想到的一个方法了,通过压缩CSS文件大小来提高页面加载速度。现在的构建工具,如webpack、gulp/grunt、rollup等也都支持CSS压缩功能。压缩后的文件能够明显减小,可以大大降低了浏览器的加载时间。同时也有以下操作可以提升性能:
1.CSS层级嵌套最好不要超过3层
一般情况下,元素的嵌套层级不能超过3级,过度的嵌套会导致代码变得臃肿,沉余,复杂。导致css文件体积变大,造成性能浪费,影响渲染的速度!而且过于依赖HTML文档结构。这样的css样式,维护起来,极度麻烦,如果以后要修改样式,可能要使用!important
覆盖。尽量保持简单,不要使用嵌套过多过于复杂的选择器。
2.删除无用CSS代码
一般情况下,会存在这两种无用的CSS代码:一种是不同元素或者其他情况下的重复代码,一种是整个页面内没有生效的CSS代码即没有使用到的多余代码。
对于前者,在编写代码的时候,我们应该尽可能地提取公共类,减少重复。对于后者,在不同开发者进行代码维护的过程中,总会产生不再使用的CSS的代码,当然一个人编写时也有可能出现这一问题。而这些无用的CSS代码不仅会增加浏览器的下载量,还会增加浏览器的解析时间,这对性能来说是很大的消耗。所以我们需要找到并去除这些无用代码。
谷歌的Chrome浏览器有可以到看到无用CSS代码的功能。只需转到查看>开发人员>开发人员工具,并在最近的版本中打开Sources选项卡,然后打开命令菜单。然后,点击Coverage,在Coverage analysis窗口中高亮显示当前页面上未使用的代码。
9.首屏问题
首屏指的是网站加载后,用户不用滚动屏幕所看到的所有信息。当项目代码量达到一定级别,我们就会发现我们首次进入项目的时间特别长,其原因有很多,优化分为两个方向:资源加载优化和页面渲染优化(网络方向的就暂不学习,如DNS等)。
页面渲染优化:
这里我们主要考虑用户体验,有以下几种方式能够一定程度上提高用户体验:
1.白屏时的loading动画
首屏优化,在JS没解析执行前,让用户能看到Loading动画,减轻等待焦虑。通常会在index.html上写简单的CSS动画,直到Vue挂载后替换挂载节点的内容,但这种做法实测也会出现短暂的白屏,建议手动控制CSS动画关闭
2.首屏骨架加载
骨架屏,也就是APP内常见的加载时各部分灰色色块,现今已经有很多成熟的骨架屏,比如vue-content-loader,这边不展开理解骨架屏原理,感兴趣的朋友可以看一下骨架屏插件实现原理。
骨架屏适合在页面加载延迟比较高的情况下使用,但是骨架屏的灰色色块需要视觉切图,这个图是需要缓存在客户端的。如果大范围使用骨架屏,可能会导致客户端缓存过多的图片,反而得不偿失吧。SSR实施成本会高一些,如果不是特别重要的页面不建议使用。 当网络环境很差劲的情况下,依然还是要做降级处理的,也就是客户端渲染。
3.渐进加载图片
一般来说,图片加载有两种方式,一种是自上而下扫描,一种则是原图的模糊展示,然后逐渐/加载完清晰。前者在网速差的时候用户体验较差,后者的渐进/交错式加载则能减轻用户的等待焦虑,带来更好的体验,可以参考 js-mozjpeg依赖。
资源加载优化:
对于资源加载的优化,可以分为体积优化、代码优化、传输优化。
1.体积优化
该方式是通过减小所加载资源的大小,来提高请求加载速度。
- 图片的格式未针对浏览器进行优化,体积比较大,小型图片可以使用svg替换 png;其他图片可进行压缩
- 排查并移除多余的依赖和静态资源
- 离线包hybrid:提前将html、css、js等下载到App内,用户在下载时便将主要内容也一同下载,当在App内打开页面时,webview使用file:// 协议加载本地的html、css、js,然后再请求数据、渲染。
2.代码优化
该方式是通过减小所加载资源的大小、以及所需文件的优先级,来提高请求加载速度或是先完成样式的加载。
- 减少dom elements的数量
- 先加载css外联文件:如js外联文件放到body底部,css外联文件放到head内
路由懒加载:SPA中一个很重要的提速手段就是路由懒加载,当打开页面时才去加载对应文件,我们利用webpack的代码分割,减小app.js的体积,从而提高首屏加载速度。
记得只在生产时懒加载,否则路由多起来后,开发时的构建速度非常影响开发-。=
const route = [ { path:'/test', component:()=>import('../../test.vue') } ]
3.传输优化
该方式是通过减少Http请求次数、提高Http响应速度、优化加载方式,来提高请求速度。
- 图片懒加载:图片不用一次性全部加载,可以等到需要使用的再进行请求加载,从而避免一开始大量的图片请求加载导致的白屏,这类插件也已经相当成熟,比如vue-lazyLoad
- 减少cookie大小可以提高获得响应的时间
采用gzip压缩传输
//vue.config.js const CompressionPlugin = require('compression-webpack-plugin'); module.exports = { configureWebpack: config => { config.plugins = [ ...config.plugins, // 开启 gzip 压缩 new CompressionPlugin({ filename: '[path][base].gz', algorithm: 'gzip', test: /\.js$|\.html$|\.css$|\.jpg$|\.jpeg$|\.png/, // 需要压缩的文件类型 threshold: 10240, minRatio: 0.8 }) ] } }
nginx配置
http { gzip on; # 开启 gzip 压缩 gzip_static on; # 若存在静态 gz 文件,则使用该文件 gzip_min_length 10k; # 设置允许压缩的页面最小字节数 gzip_buffers 16 8k; # 设置用于处理请求压缩的缓冲区数量和大小 gzip_comp_level 1; # 设置压缩级别 1-9,数字越大,压缩后的大小越小,也越占用CPU,花费时间越长 # 对特定的 MIME 类型生效, 其中'text/html’被系统强制启用 gzip_types application/javascript text/css font/ttf font/x-woff; gzip_vary on; # 是否在 http header中 添加 Vary:Accept-Encoding, on | off gzip_http_version 1.1; # 在 http/1.1 的协议下不开启压缩 }
资源响应头中出现 Content-Encoding: gzip 则代表配置成功
CDN:采用CDN引入,在index.html使用CDN引入,并在webpack配置。打包之后webpack进会从外部打包第三方引入的库,减小app.js的体积,从而提高首屏加载速度。
<!-- 使用CDN的CSS文件 --> <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" /> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /> <% } %> <!-- 使用CDN加速的JS文件,配置在vue.config.js下 --> <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> <script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> <% } %>
- 开启HTTP2:HTTP2是HTTP协议的第二个版本,相较于HTTP1 速度更快、延迟更低,功能更多。 目前来看兼容性方面也算过得去,在国内有超过50%的覆盖率。通常浏览器在传输时并发请求数是有限制的,超过限制的请求需要排队,以往我们通过域名分片、资源合并来避开这一限制,而使用HTTP2协议后,其可以在一个TCP连接分帧处理多个请求(多路复用),不受此限制。(其余的头部压缩等等也带来了一定性能提升)如果网站支持HTTPS,请一并开启HTTP2,成本低收益高,对于请求多的页面提升很大,尤其是在网速不佳时。
- 服务端渲染SSR:对服务器性能消耗较高,项目复杂度变高,出问题排查起来比较麻烦。
最后
走过路过,不要错过,点赞、收藏、评论三连~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。