性能优化不单指优化一个页面的打开速度,在开发环境将一个项目的启动时间缩短使开发体验更好也属于性能优化,大文件上传时为其添加分片上传、断点续传也属于性能优化。在项目开发以及用户使用的过程中,能够让任何一个链路快一点,都可以被叫做性能优化。
本文会对web页面的全链路进行完整的讲解并针对每一步找到能做的性能优化点,本文的目标是极致的性能优化。
因为针对性能优化,能做的点会特别特别的多,覆盖着整个互联网的访问流程,因此此文章的内容会比较多且杂,笔者会尽量对内容进行分类讲解。
本文的大致流程为先讲理论知识,比如如何评价一个页面的性能好与不好、如果获取性能指标,如何使用各种性能相关工具,浏览器如何获取并渲染页面。笔者认为这些都是基础,只有了解了这些基础才能开始考虑如何去优化。
接下来我们会进入性能优化环节,在这个环节我会详细讲解在页面的整个流程中,哪些地方可以做哪些优化。
进程与线程
输入url到页面展示完整过程
1.用户输入
2.卸载原页面并重定向到新页面
3.处理Service Worker
4.网络请求
5.服务端响应
6.浏览器渲染详细流程
浏览器处理每一帧的流程
Chrome Performance(性能)
Chrome Performance 工具的使用
Performance API介绍
使用Performance API获取性能相关指标
Coverage(覆盖率)
Lighthouse
Network(网络)
网络请求中的Timing(时间)
网络请求的优先级
网页总资源信息
Network配置
网络优化策略
减少HTTP请求数
使用HTTP缓存
使用 HTTP/2.0
避免重定向
使用 dns-prefetch
使用域名分片
CDN
压缩
使用contenthash
合理使用preload、prefetch
浏览器渲染优化策略
关键渲染路径
强制同步布局问题
如何减少重排与重绘
静态文件优化策略
图片格式
图片优化
HTML优化
CSS优化
JS优化
字体优化
浏览器储存优化策略
Cookie
LocalStorage
SessionStorage
IndexDB
其他优化策略
使用PWA提高用户体验
我们需要知道浏览器是如何渲染一个页面的,我们才能知道如何对页面进行性能优化,所以这里我们对一些基础知识进行讲解
浏览器有多种进程,其中最主要的5种进程如下
图1
图1
用户在浏览器进程
输入并按下回车健后,浏览器判断用户输入的url是否为正确的url,如果不是,则使用默认的搜索引擎将该关键字拼接成url。
然后浏览器会将现有页面卸载掉并重定向到用户新输入的url页面,也就是图中【Process Unload Event】和【Redirect】流程。
此时浏览器会准备一个渲染进程
用于渲染即将到来的页面,和一个网络进程
用于发送网络请求。
如果当前页面注册了Service Worker那么它可以拦截当前网站所有的请求,进行判断是否需要向远程发送网络请求。也就是图中【Service Worker Init】与【Service Worker Fecth Event 】步骤
如果不需要发送网络请求,则取本地文件。如果需要则进行下一步。
OSI网络七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
在实际应用中物理层、数据链路层被统称为物理层,会话层、表示层、应用层被统称为应用层,所以实际使用时通常分为4个层级
【物理层】>【网络层(IP)】>【传输层(TCP/UDP)】>【应用层(HTTP)】
也就是图中【HTTP Cache】、【DNS】、【TCP】、【Request】、【Response】步骤
图2
图2
浏览器会拿着url通过网络进程
进行如下步骤
HTTP协议的各个版本特性如下:
HTTP/0.9
没有请求头和响应头,不区分传输的内容类型,因为当时只传输HTML。HTTP/1.0
提供了请求头和响应头,可以传输不同类型的内容数据。根据请求响应头的不同来处理不同的资源,HTTP1.0每次发完请求都会断开TCP连接。有新的请求时再次创建TCP连接。HTTP/1.1
默认开启了 keep-alive ,它能够让一个TCP连接中传输多个HTTP请求,也叫链路复用
。但一个TCP连接同一时间只能发送一个HTTP请求,为了不阻塞多个请求,Chrome允许创建6个TCP连接,所以在HTTP/1.1中,最多能够同时发送6个网络请求。HTTP/2.0
HTTP/2.0使用同一个TCP连接来发送数据,他把多个请求通过二进制分贞层实现了分贞,然后把数据传输给服务器。也叫多路复用
,多个请求复用同一个TCP连接。HTTP/2.0会将所有以:
开头的请求头做一个映射表,然后使用hpack
进行压缩,使用这种方式会使请求头更小。服务器可主动推送数据给客户端。HTTP/3.0
使用UDP实现,在UDP上一层加入一层QUIC
协议,解决了TCP协议中的队头阻塞问题。6 . 服务器收到数据后解析HTTP请求(请求行/请求头/请求体),处理完成后生成状态码和HTTP响应(响应行/响应头/响应体)后返回给客户端,也就是图2的【等待中】在做的事情。
7 . 客户端接收到HTTP响应后根据状态码进行对应的处理,如果状态码为304则直接代表协商缓存生效,直接取本地的缓存文件。如果不是则下载内容。也就是图1中【Response】以及图2中的【下载内容】步骤。
在4.网络请求
第6
步中,服务器收到HTTP请求后需要根据请求信息来进行解析,并返回给客户端想要的数据,这也就服务端响应。
服务端可以响应并返回给客户端很多种类型的资源,这里主要介绍html
类型
目前前端处理服务端响应html请求主要分为SSR服务端渲染与CSR客户端渲染,CSR就是返回一个空的HTML模版,然后浏览器加载js后通过js动态渲染页面。SSR是服务端在接受到请求时事先在服务端渲染好html返回给客户端后,客户端再进行客户端激活。
在打开一个站点的首屏页的完整链路中,使用SSR服务端渲染时的速度要远大于CSR客户端渲染,并且SSR对SEO友好。所以对于首屏加载速度比较敏感或者需要优化SEO的站点来说,使用SSR是更好的选择。
浏览器渲染详细流程主要在4.网络请求
中的地7
步。浏览器下载完html
内容后进行解析何渲染页面的流程。
渲染流程分为4种情况,
<div />
)时已经解析完CSS相关标签<div />
)时CSS相关标签尚未解析完下面的流程是对上图的文字版解析。读者可将以上4种情况分别带入到如下的渲染流程中走一遍。就能理解浏览器的完整渲染过程了。
浏览器收到html资源后先预扫描<link />
和<script />
并加载对应资源
对HTML字符串从上到下逐行解析,每解析完成一部分都会拿着解析结果进入下一步骤
css
相关标签跳过此步骤
如果当前解析结果为<div />
相关标签,则生成DOM树(window.document
)后进入下一步。
如果当前解析结果为<script />
相关标签且并且没有添加异步属性,则先停止【HTML Parser】的进行,等待<script />
资源加载完成后,然后按照以下2种情况处理,当处理完成后便停止当前<script />
标签后续步骤的执行,并继续进行新标签【HTML Parser】步骤的解析
css
相关节点则立即执行<script />
。(此时页面会把<script />
之前的内容都显示在页面上)css
相关节点则等待css
相关节点解析完成后再执行<script />
。(在CSS解析完的一瞬间会触发之前所有等待CSS资源解析的任务,假如在解析<script />
之前还有<div />
的话,理论上<div />
应该在执行<script />
之前被绘制到页面上,但因为Chrome是按照贞为单位来进行元素的绘制的,如果绘制<div />
与执行<script />
的时间在一贞之内,则会因为在绘制<div />
时被js
阻塞,所以实际上需要等js
执行完才会实际完成<div />
的绘制)<div />
相关标签跳过此步骤
如果当前解析结果为css
相关标签,则等待其CSS资源加载完成,同时继续进行下一行的【HTML Parser】
<div />
相关标签跳过此步骤
当CSS资源加载完毕后,对CSS从上到下逐行解析
<div />
相关标签跳过此步骤
当CSS解析完毕后,生成CSS规则树,也叫CSSOM,也就是window.document.styleSheets
根据DOM树与CSS规则树计算出每个节点的具体样式。
分为两种情况
<div />
相关节点如果HTML从未解析到过css
相关标签则使用HTML默认样式,如果已经解析到过css
相关标签则阻塞等待css
标签也完成【Attachment】步骤后才进入下一步。
css
相关节点则需要根据是否在之前已经渲染过CSS资源中对应的DOM节点,如果已经渲染过则需要重绘。如果未渲染过任何相关DOM节点则此步骤为最后一步。
生成渲染树,在此阶段已经可以将具体的某个<div />
与对应的CSS样式对应起来了。有了渲染树后浏览器就能根据当前浏览器的状态计算出某个DOM节点的样式、大小、宽度、是否独占一行等信息。计算完成后把一些不需要显示出来的节点在渲染树中删掉。如display: none
。
通过渲染树进行分层(根据定位属性、透明属性、transform属性、clip属性等)生成图层树。
绘制所有图层,并转交给合成线程来最最终的合并所有图层的处理。
最终生成页面并显示到浏览器上
浏览器在渲染完页面之后还需要不间断的处理很多内容的,比如动画、用户事件、定时器等。因此当浏览器渲染完页面后,还会在之后的每一帧到来时执行以下的流程。
touch
和wheel
事件,后处理【非阻塞事件Non-blocking】包括click
和keypress
requestAnimationFrame
回调函数和IntersectionObserver
回调函数。了解完浏览器渲染原理,我们还需要知道根据哪些指标才能判断一个页面性能的好坏,在Chrome中这些指标应该怎么获取。以及Chrome都为我们提供了哪些性能相关的工具,如何使用。
Performance既是一个Chrome工具,可用于性能检测。
同样又是一套JS API,可在Chrome中执行。
打开Chrome调试面板选择Performance,中文版为性能,点击刷新按钮,Performance会刷新并录制当前页面,然后我们就可以在面板中看到如下的各种性能相关细节。
js中存在Performance API,可用于性能检测,具体如下
navigationStart
导航开始。redirectStart
然后将当前页面重定向到用户新输入的url页面。完成重定向后会执行redirectEnd
workerStart
对Service Worker进行初始化操作。fetchStart
domainLookupStart
然后去解析DNS,解析完会执行domainLookupEnd
contentStart
,然后进行TCP三次握手,如果是HTTPS则执行secureConnectionStart
进行SSL协商。完成后会执行contentEnd
。requestStart
,然后开始真正的发送请求responseStart
,响应全部接收完毕后会执行responseEnd
domLoading
开始加载dom,dom加载完毕后执行domInteractive
,此时dom已经可以交互。然后执行domContentLoadedEventStart
,当dom整个节点全部加载完毕并执行完DOMContentLoaded
事件后会触发domContentLoadedEventEnd
简称DCL
当dom整个加载完成会执行domComplete
,此时页面资源已经全部加载完毕。loadEventStart
,触发window.onload
事件,load
事件完成后会执行loadEventEnd
。接下来我们来了解一下目前常用的性能指标,并且我们需要知道其中一些关键指标如何用Performance API获取。
TTFB(Time To First Byte)
: 从发送请求到数据返回第一个字节所消耗的时间
const { responseStart, requestStart } = performance.timing
const TTFB = responseStart - requestStart
FP (First Paint) 首次绘制
: 第一个像素绘制到页面上的时间
const paint = performance.getEntriesByType('paint')
const FP = paint[0].startTime
FCP (First Contentful Paint) 首次内容绘制
标记浏览器渲染来自 DOM 第一位内容的时间点,该内容可能是文本、图像、SVG 甚至 元素.
const paint = performance.getEntriesByType('paint')
const FCP = paint[1].startTime
FMP(First Meaningful Paint) 首次有效绘制
: 例如,在 YouTube 观看页面上,主视频就是主角元素.
图片可以没加载完成,但整体的骨架已经加载完成了。
1秒内完成FMP的概率超过80%,那就代表这个网站是一个性能较好的网站
let FMP = 0
const performanceObserverFMP = new PerformanceObserver((entryList, observer) => {
const entries = entryList.getEntries()
observer.disconnect()
FMP = entries[0].startTime
})
// 需要在元素中添加 elementtiming="meaningful"
performanceObserverFMP.observe({ entryTypes: ['element'] })
TTI (Time to Interactive) 可交互时间
: DOM树构建完毕,可以绑定事件的时间
const { domInteractive, fetchStart } = performance.timing
const TTI = domInteractive - fetchStart
LCP (Largest Contentful Paint) 最大内容渲染
: 代表在viewport中最大的页面元素加载的时间. LCP的数据会通过PerformanceEntry对象记录, 每次出现更大的内容渲染, 则会产生一个新的PerformanceEntry对象.(2019年11月新增)
let LCP = 0
const performanceObserverLCP = new PerformanceObserver((entryList, observer) => {
const entries = entryList.getEntries()
observer.disconnect()
LCP = entries[entries.length - 1].startTime
})
performanceObserverLCP.observe({ entryTypes: ['largest-contentful-paint'] })
DCL (DomContentloaded)
: 当 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,无需等待样式表、图像和子框架的完成加载
const { domContentLoadedEventEnd, fetchStart } = performance.timing
const DCL = domContentLoadedEventEnd - fetchStart
L (onLoad)
, 当依赖的资源(图片、文件等), 全部加载完毕之后才会触发
const { loadEventStart, fetchStart } = performance.timing
const L = loadEventStart - fetchStart
FID (First Input Delay) 首次输入延迟
: 指标衡量的是从用户首次与您的网站进行交互(即当他们单击链接,点击按钮等)到浏览器实际能够访问之间的时间
let FID = 0
const performanceObserverFID = new PerformanceObserver((entryList, observer) => {
const entries = entryList.getEntries()
observer.disconnect()
FID = entries[0].processingStart - entries[0].startTime
})
performanceObserverFID.observe({ type: ['first-input'], buffered: true })
TBT (Total Blocking Time) 页面阻塞总时长
: TBT汇总所有加载过程中阻塞用户操作的时长,在FCP和TTI之间任何long task中阻塞部分都会被汇总
CLS (Cumulative Layout Shift) 累积布局偏移
: 总结起来就是一个元素初始时和其hidden之间的任何时间如果元素偏移了, 则会被计算进去, 具体的计算方法可看这篇文章 https://web.dev/cls/
SI (Speed Index)
: 指标用于显示页面可见部分的显示速度, 单位是时间
获取代码未使用占比
获取性能报告并查看推荐优化项
可以在本地安装命令行工具来使用,也可以通过Chrome来使用。
命令行方式使用
npm install -g lighthouse
lighthouse --view https://m.baidu.com
在Chrome中使用
能获取网络请求的时间消耗细节,可以根据耗时来决定优化策略。优先优化耗时最长的
【正在排队】网络请求队列的排队时间
【已停止】阻塞住用于处理其他事情的时间
【DNS查找】用于DNS解析IP地址的时间
【初始连接】创建TCP连接时间
【SSL】用于SSL协商的时间
【已发送请求】用于发送请求的时间
【等待中】请求发出至接收响应的时间也可以理解为服务端处理请求的时间
【下载内容】下载响应的时间
浏览器会根据资源的类型决定优先请求哪些资源,优先级高的请求能够优先被加载。
右击此处勾选优先级可打开优先级功能,在请求中便可看到网络请求的优先级
不同资源类型的优先级排序如下
【最高】html、style
【高】font、fetch、script
【低】image、track
【58个请求】网页一共多少个请求
【6.9 MB 项资源】网页资源一共6.9MB大小
【DOMContentLoaded:454 毫秒】DOM加载完毕的时长
【加载时间:1.02 秒】onload完毕的时长
上面我们分别讲解了进程与线程、浏览器打开一个页面的完整过程、浏览器处理每一帧时的流程、Chrome性能相关的各种工具以及性能相关的各种指标。以上内容都掌握之后我们再考虑性能优化遍有了思路,我们在页面展示的任意一个步骤中对其进行优化都能对整个网页的展示性能产生影响。
下面列出了一个页面能优化的所有内容,读者可根据自己的业务情况结合性能工具来做适合自己的优化方式。
合并JS、合并CSS、合理内嵌JS和CSS、使用雪碧图
使用强制缓存可以不走网络请求,直接走本地缓存数据来加载资源。
使用协商缓存可以减少数据传输,当不需要更新数据时可通知客户端直接使用本地缓存。
HTTP/2.0使用同一个TCP连接来发送数据,他把多个请求通过二进制分贞层实现了分贞,然后把数据传输给服务器。也叫多路复用
,多个请求复用同一个TCP连接。
HTTP/2.0会将所有以:
开头的请求头做一个映射表,然后使用hpack
进行压缩,使用这种方式会使请求头更小。
服务器可主动推送数据给客户端。
301、302 重定向会降低响应速度
DNS请求虽然占用的带宽较少,但会有很高的延迟,由其在移动端网络会更加明显。
使用dns-prefetch可以对网站中使用到的域名提前进行解析。提高资源加载速度。
通过dns预解析技术可以很好的降低延迟,在访问以图片为主的移动端网站时,使用DNS预解析的情意中下页面加载时间可以减少5%。
<link rel="dns-prefetch" href="https://a.hagan.com/">
在HTTP/1.1中,一个域名同时最多创建6个TCP连接,将资源放在多个域名下可提高请求的并发数
静态资源全上CDN,CDN能非常有效的加快网站静态资源的访问速度。
gzip压缩、html压缩、js压缩、css压缩、图片压缩
contenthash可以根据文件内容在文件名中加hash,可用于浏览器缓存文件,当文件没有改变时便直接取本地缓存数据
preload预加载、prefetch空闲时间加载
<link rel="preload" as="style" href="/static/style.css">
<link rel="preload" as="font" href="/static/font.woff">
<link rel="preload" as="script" href="/static/script.js">
<link rel="prefetch" as="style" href="/static/style.css">
<link rel="prefetch" as="font" href="/static/font.woff">
<link rel="prefetch" as="script" href="/static/script.js">
两者都不会阻塞onload
事件,prefetch
会在页面空闲时候再进行加载,是提前预加载之后可能要用到的资源,不一定是当前页面使用的,preload
预加载的是当前页面的资源。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preload" as="style" href="./preload.css">
<title>Document</title>
</head>
<body>
<article></article>
</body>
</html>
如上代码,预加载了css但并没有使用。浏览器在页面 onload
完成一段时间后,发现还没有引用预加载的资源时,浏览器会在控制台输出下图的提示信息
preload和prefetch可根据资源类型决定资源加载的优先级,详细优先级如代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 最高 -->
<link rel="preload" as="style" href="./file.xxx">
<!-- 高 -->
<link rel="preload" as="font" href="./file.xxx">
<link rel="preload" as="fetch" href="./file.xxx">
<link rel="preload" as="script" href="./file.xxx">
<!-- 低 -->
<link rel="preload" as="image" href="./file.xxx">
<link rel="preload" as="track" href="./file.xxx">
<title>Document</title>
</head>
<body>
<article></article>
</body>
</html>
当通过JS或者其他任意方式修改DOM后,浏览器会进入如下流程
【JS通过API修改DOM】>【计算样式】>【布局(重排)】>【绘制(重绘)】>【合成】
Reflow 重排
:重排在Chrome Performance中叫做布局,通常添加或删除元素、修改元素大小、移动元素位置、获取位置信息都会触发页面的重排,因为重排可能会改变元素的大小位置等信息,这样的改变会影响到页面大量其它元素的大小位置信息,会耗费掉大量的性能,所以在实际应用中我们应该尽可能的减少重排
Repaint 重绘
:重绘在Chrome Performance中叫做绘制,通常样式改变但没有影响位置时会触发重绘操作,重绘性能还好,但我们也需要尽量减少重绘,如果需要做一些动画,我们尽量使用CSS3动画,CSS3动画只需要在初始化时绘制一次,之后的动画都不会触发重绘操作。
在同一个函数内,修改元素后又获取元素的位置时会触发强制同步布局,影响渲染性能
强制同步布局会使js强制将【计算样式】和【布局(重排)】操作提前到当前函数任务中,这样会导致每次运行时执行一次【计算样式】和【重排】,这样一定会影响页面渲染性能,而正常情况下【计算样式】和【重排】操作会在函数结束后统一执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<article id="article"></article>
<script>
const domArticle = document.querySelector('#article')
// const { offsetTop } = domArticle
function reflow () {
const domH1 = document.createElement('h1')
domH1.innerHTML = 'h1'
domArticle.appendChild(domH1)
/**
* 强制同步布局
* 在修改元素后又获取元素的位置时会触发强制同步布局,影响渲染性能
* 解决办法是采用读写分离的原则,同一个函数内只读、只写
*/
const { offsetTop } = domArticle
console.log(offsetTop)
}
window.onload = () => {
for (let i = 0; i < 10; i++) {
reflow()
}
}
</script>
</body>
</html>
在函数运行时执行了10次【计算样式】和【重排】
反复触发强制同步布局也叫布局抖动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<article id="article"></article>
<script>
const domArticle = document.querySelector('#article')
const { offsetTop } = domArticle
function reflow () {
const domH1 = document.createElement('h1')
domH1.innerHTML = 'h1'
domArticle.appendChild(domH1)
/**
* 强制同步布局
* 在修改元素后又获取元素的位置时会触发强制同步布局,影响渲染性能
* 解决办法是采用读写分离的原则,同一个函数内只读、只写
*/
// const { offsetTop } = domArticle
console.log(offsetTop)
}
window.onload = () => {
for (let i = 0; i < 10; i++) {
reflow()
}
}
</script>
</body>
</html>
我们遵循读写分离的原则,将读取位置操作放到函数外,我们可以发现就算循环插入10个dom节点,也只需要执行一次【计算样式】和【重排】。
will-change: transform;
将元素独立为一个单独的图层。(定位、透明、transform、clip都会产生独立图层)适合色彩丰富的图、Banner图。不适合:图形文字、图标、不支持透明度。
适合纯色、透明、图标,支持纯透明和半透明。不适合色彩丰富图片,因为无损储存会导致储存体积大于jpeg
适合动画、可以动的图标。支持纯透明但不支持半透明,不适合色彩丰富的图片。
埋点信息通常也会使用gif发送,因为1x1的gif图发送的网络请求比普通的get请求要小一些。
支持纯透明和半透明,可以保证图片质量和较小的体积,适合Chrome和移动端浏览器。不适合其他浏览器。
矢量格式,大小非常小,但渲染成本过高,适合小且色彩单一的图标。
alt="xxx"
属性,图像无法显示时会显示alt
内容loading="lazy"
为原生,建议使用IntersectionObserver
自己做懒加载srcset
与sizes
的使用。<html lang="en">``<meta charset="UTF-8">
background-color: expression(...)
<link rel="stylesheet" href="./small.css" media="screen and (max-width:600px)" />
<link rel="stylesheet" href="./big.css" media="screen and (min-width:601px)"/>
contain
属性,能控制对应元素是否根据子集元素的改变进行重排@import
使用,因为它使用串行加载IntersectionObserver
实现<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
img {
height: 200px;
display: block;
}
</style>
<title>Document</title>
</head>
<body>
<img src="./loading.gif" data-src="./01.jpg" />
<img src="./loading.gif" data-src="./02.jpg" />
<img src="./loading.gif" data-src="./03.jpg" />
<img src="./loading.gif" data-src="./04.jpg" />
<img src="./loading.gif" data-src="./05.jpg" />
<img src="./loading.gif" data-src="./06.jpg" />
<img src="./loading.gif" data-src="./07.jpg" />
<img src="./loading.gif" data-src="./08.jpg" />
<img src="./loading.gif" data-src="./09.jpg" />
<img src="./loading.gif" data-src="./10.jpg" />
<script>
const intersectionObserver = new IntersectionObserver((changes) => {
changes.forEach((item, index) => {
if (item.intersectionRatio > 0) {
intersectionObserver.unobserve(item.target)
item.target.src = item.target.dataset.src
}
})
});
const domImgList = document.querySelectorAll("img");
domImgList.forEach((domImg) => intersectionObserver.observe(domImg));
</script>
</body>
</html>
requestAnimationFrame
来做动画,使用requestIdleCallback
来进行空闲时的任务处理FOUT(Flash of Unstyled Text)等待一段时间,如果没加载完成,先显示默认。加载 后再进行切换。
FOIT(F1ash of Invisib1e Text) 字体加载完毕后显示,加载超时降级系统字体(白 屏
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@font-face {
font-family: 'hagan';
src: url('./font.ttc');
font-display: swap;
/* b1ock 35 内不显示,如果没加载完毕用默认的 */
/* swap 显示老字体 在替换*/
/* fa11back 缩短不显示时间,如果没加载完毕用默认的,和b1ock类似*
/* optional 替换可能用字体 可能不替换*/
}
article {
font-family: hagan;
}
</style>
<title>Document</title>
</head>
<body>
<article>ABC abc</article>
</body>
</html>
cookie
在过期之前一直有效,最大储存大小为4k,限制字段个数,不适合大量的数据储存,每次请求会携带cookie
,主要用来做身份校验。
优化方式:
cookie
有效期cookie
来减少cookie
传输cookie
域名采用不同域名,避免静态资源请求携带cookie
。Chrome下最多储存5M,除非手动清除,否则一直存在。可以利用localStorage
储存静态资源。比如储存网页的.js
、.css
,这样会使页面打开速度非常快。例如 https://m.baidu.com
/index.js
const name = 'hagan'
function showName () {
console.log(name)
}
showName()
/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://lib.baomitu.com/axios/0.26.1/axios.js"></script>
<script>
cacheFile('/index.js')
async function cacheFile (url) {
const fileContent = localStorage.getItem(url)
if (fileContent) {
eval(fileContent)
} else {
const { data } = await axios.get(url)
eval(data)
localStorage.setItem(url, data)
}
}
</script>
</body>
</html>
会话级别储存,可用于页面间的传值
浏览器的本地数据库,大小几乎无上限
webapp用户体验差的一大原因是不能离线访问。用户粘性低的一大原因是无法保存入口,PWA就是为了解决webapp的用户体验问题而诞生的。使用PWA能令站点拥有快速、可靠、安全等特性。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8