【前端性能】性能优化手段-高频面试题
持续更新.............................最近更新2023/10/24
1. 讲一下png8、png16、png32的区别,并简单讲讲 png 的压缩原理
PNG8、PNG16、PNG32 是 PNG 图像格式的不同变种,它们主要区别在于颜色深度和透明度支持的不同。
区别
PNG8: PNG8 是一种 8 位颜色深度的 PNG 图像格式,它最多支持 256(2^8) 种颜色。对于颜色相对较简单、不需要透明度的图像,使用 PNG8 可以获得较小的文件大小。PNG8 图像使用一种叫做调色板(Palette)的技术来存储颜色信息,它会创建一个包含所有使用的颜色的列表,并在图像中通过索引来引用这些颜色。
PNG16: PNG16 是一种 16 位颜色深度的 PNG 图像格式,它支持更多的颜色,最多可达 655369(2^16)种颜色。PNG16 适用于一些颜色较丰富的图像,但同样不支持透明度。
PNG32: PNG32 是一种 32 位颜色深度的 PNG 图像格式,它支持上千万种颜色,并且支持完全透明度(alpha 通道)。PNG32 适用于需要精确透明度控制的图像,例如图标、Logo 等。
原理
PNG 图像使用无损压缩技术来减小文件大小。它主要通过以下两种压缩方式来实现:
-
使用 DEFLATE 算法:PNG 使用 DEFLATE 算法来对图像数据进行压缩,这是一种无损的压缩算法,可以将相邻的相似数据块识别并用更短的编码进行替代,从而减小文件大小。
1.1 扩展: DEFLATE算法是一种用于数据压缩的无损压缩算法。它是由Phil Katz于1993年创建的,最初用于PKZIP压缩工具中。DEFLATE算法结合了两种压缩技术:LZ77(Lempel-Ziv 77)和哈夫曼编码。
LZ77是一种基于字典的压缩技术,它通过查找和替换重复的数据来实现压缩。它使用滑动窗口和查找缓冲区来识别重复的数据,并用指向先前出现的相同数据的指针来替换它们。
哈夫曼编码是一种变长编码技术,它使用较短的编码表示频率较高的字符,而使用较长的编码表示频率较低的字符。这种编码方式可以有效地减少数据的存储空间。
DEFLATE算法首先使用LZ77压缩数据,然后使用哈夫曼编码对压缩后的数据进行进一步压缩。这种组合技术使得DEFLATE算法能够在保持数据完整性的同时,显著减小数据的大小。因此,DEFLATE算法在许多应用中被广泛使用,如ZIP文件压缩、HTTP协议中的gzip压缩等。 -
使用索引颜色和调色板:对于 PNG8 图像,它使用调色板技术来存储颜色信息。调色板是一个包含所有使用的颜色的列表,然后图像中使用颜色的索引来引用调色板中的颜色。这样可以大大减小文件大小,特别适用于颜色较简单的图像。
**注意:**由于 PNG 使用无损压缩,所以图像的质量不会因为压缩而损失,但这也导致 PNG 文件相对于其他有损压缩格式(如 JPEG)通常会更大。因此,在选择使用 PNG 还是其他格式时,需要根据图像类型、透明度需求和文件大小要求进行权衡。
2. 页面加载的过程中,JS 文件是不是一定会阻塞 DOM 和 CSSOM 的构建?
不一定。
JavaScript阻塞DOM和CSSOM的构建的情况主要集中在以下两个方面:
-
JavaScript文件被放置在head标签内部
当JavaScript文件被放置在head标签内部时,浏览器会先加载JavaScript文件并执行它,然后才会继续解析HTML文档。因此,如果JavaScript文件过大或服务器响应时间过长,就会导致页面一直处于等待状态,进而影响DOM和CSSOM的构建。 -
JavaScript代码修改了DOM结构
在JavaScript代码执行时,如果对DOM结构进行了修改,那么浏览器需要重新计算布局(reflow)和重绘(repaint),这个过程会较为耗时,并且会阻塞DOM和CSSOM的构建。
除此之外,还有一些情况下JavaScript并不会阻塞DOM和CSSOM的构建
:
- 通过设置 script 标签的
async 、defer
属性避免阻塞DOM和CSSOM的构建
- async:异步加载JavaScript文件,脚本的下载和执行将与其他工作同时进行(例如从服务器请求其他资源、渲染页面等),而不必等到脚本下载完成才开始这些操作。因此,在使用 async 属性时,脚本的加载和执行是异步的,并且不保证脚本在页面中的顺序。
- defer属性 :属性告诉浏览器立即下载脚本文件,但有一个重要的区别:当文档解析时,脚本不会执行,直到文档解析完成后才执行。这意味着脚本将按照它们在页面上出现的顺序执行,并且在执行之前,整个文档已经被解析完毕了。
- Web Workers :Web Workers 是一种运行在后台线程的JavaScript脚本,它不会阻塞DOM和CSSOM的构建,并且可以利用多核CPU提高JavaScript代码执行速度。
总结
在一定情况下,JavaScript的执行会阻塞DOM和CSSOM的构建。
但是,在实际应用中,我们可以通过设置 script 标签的 async、defer 属性、使用Web Workers等方式来避免这个问题。
3. 导致页面加载白屏时间长的原因有哪些,怎么进行优化?
一、白屏时间 (FCP(First Contentful Paint))
白屏时间:即用户点击一个链接或打开浏览器输入URL地址后,从屏幕空白到显示第一个画面的时间。
二、白屏时间的重要性
当用户点开一个链接或者是直接在浏览器中输入URL开始进行访问时,就开始等待页面的展示。页面渲染的时间越短,用户等待的时间就越短,用户感知到页面的速度就越快。这样可以极大的提升用户的体验,减少用户的跳出,提升页面的留存率。
三、白屏的过程
从输入url,到页面的画面展示的过程
1、首先,在浏览器地址栏中输入url
2、浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作。
3、在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址。
4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。
5、握手成功后,浏览器向服务器发送http请求,请求数据包。
6、服务器处理收到的请求,将数据返回至浏览器
7、浏览器收到HTTP响应
8、读取页面内容,浏览器渲染,解析html源码
9、生成Dom树、解析css样式、js交互,渲染显示页面
浏览器下载HTML后,首先解析头部代码,进行样式表下载,然后继续向下解析HTML代码,构建DOM树,同时进行样式下载。当DOM树构建完成后,立即开始构造CSSOM树。理想情况下,样式表下载速度够快,DOM树和CSSOM树进入一个并行的过程,当两棵树构建完毕,构建渲染树,然后进行绘制。
Tips:浏览器安全解析策略对解析HTML造成的影响:
当解析HTML时遇到内联JS代码,会阻塞DOM树的构建,会先执行完JS代码;当CSS样式文件没有下载完成时,浏览器解析HTML遇到了内联JS代码,此时,浏览器暂停JS脚本执行,暂停HTML解析。直到CSS文件下载完成,完成CSSOM树构建,重新恢复原来的解析。
JavaScript 会阻塞 DOM 生成,而样式文件又会阻塞 JavaScript 的执行,所以在实际的工程中需要重点关注 JavaScript 文件和样式表文件,使用不当会影响到页面性能的。
四、白屏-性能优化
1. DNS解析优化
针对DNS Lookup环节,我们可以针对性的进行DNS解析优化。
- DNS缓存优化
- DNS预加载策略
- 稳定可靠的DNS服务器
DNS缓存优化
是一种提高网络性能和减少延迟的技术。DNS(Domain Name System)是将域名转换为IP地址的系统,它在互联网中起到了重要的作用。当我们访问一个网站时,计算机需要向DNS服务器查询该域名对应的IP地址,然后才能建立连接。
DNS缓存优化的目标是减少DNS查询的次数,从而加快网页加载速度。以下是一些常见的DNS缓存优化方法:
- 本地DNS缓存:操作系统和浏览器通常会在本地缓存最近查询的DNS记录。这样,当再次访问相同的域名时,就可以直接从本地缓存中获取IP地址,而无需进行DNS查询。
DNS服务器缓存:DNS服务器也会缓存最近查询的DNS记录。当多个用户查询相同的域名时,DNS服务器可以直接返回缓存的结果,而无需再次查询。- TTL设置:每个DNS记录都有一个TTL(Time-to-Live)值,表示该记录在缓存中的有效时间。通过设置适当的TTL值,可以控制DNS记录在缓存中的存储时间。较短的TTL值可以使DNS记录更频繁地更新,从而及时获取最新的IP地址。
- DNS负载均衡:一些网站使用多个IP地址来提供服务,并通过DNS负载均衡将流量分散到不同的服务器上。这样可以减轻单个服务器的负载,并提高响应速度。
- CDN(Content Delivery Network):CDN是一种分布式网络架构,它将网站的内容缓存在全球各地的服务器上。当用户访问网站时,CDN会根据用户的地理位置选择最近的服务器提供内容,从而减少网络延迟。
通过使用这些DNS缓存优化方法,可以显著提高网页加载速度和用户体验。
DNS预加载
是一种优化网页加载速度的策略,它通过在浏览器中预先解析域名的IP地址,以减少DNS查询的时间延迟。以下是几种常见的DNS预加载策略:
<link rel="dns-prefetch" href="...">
:在HTML文档的头部添加标签,使用rel属性设置为dns-prefetch,并在href属性中指定要预加载的域名。浏览器会在加载页面时提前解析这些域名的IP地址。
示例:<head> <link rel="dns-prefetch" href="https://example.com"> <link rel="dns-prefetch" href="https://cdn.example.com"> </head>
HTTP响应头
:服务器可以在HTTP响应头中添加Link字段,指示浏览器预加载特定域名的DNS信息。
示例:Link: </assets/styles.css>; rel=preload; as=style, </assets/script.js>; rel=preload; as=script, </api/data>; rel=preload; as=fetch
DNS预解析
:在CSS样式表中使用@import或url()函数引用外部资源时,浏览器会自动进行DNS预解析。@import url("https://fonts.googleapis.com/css?family=Open+Sans");
需要注意的是,DNS预加载并不适用于所有情况,它主要适用于那些在页面加载过程中需要进行大量DNS查询的情况。在使用DNS预加载时,应谨慎选择需要预加载的域名,避免过度预加载导致不必要的网络请求。此外,不同浏览器对DNS预加载的支持程度可能有所不同,因此在使用时需要进行兼容性测试。
2. TCP网络链路优化
- 调整TCP窗口大小:TCP窗口大小决定了在发送数据之后需要等待确认的时间。通过增大TCP窗口大小,可以提高网络吞吐量和传输速度。但是,过大的窗口大小可能导致网络拥塞,因此需要根据网络状况进行调整。
- 使用拥塞控制算法:TCP拥塞控制算法用于检测网络拥塞并调整发送速率。常见的拥塞控制算法包括TCP Reno、TCP Cubic等。选择适合网络环境的拥塞控制算法可以提高网络的稳定性和吞吐量。
- 启用快速重传和快速恢复:快速重传和快速恢复是TCP的一种机制,用于快速恢复丢失的数据包而不必等待超时。通过启用这些机制,可以减少数据传输的延迟和丢包的影响。
- 启用Selective Acknowledgment(SACK):SACK是一种TCP扩展,允许接收方向发送方报告丢失的数据段,从而避免不必要的重传。启用SACK可以提高网络的吞吐量和传输效率。
- 使用TCP加速器或优化器:TCP加速器或优化器是一种网络设备或软件,用于优化TCP连接的性能。它们可以通过压缩数据、缓存重复数据、优化传输路径等方式来提高网络性能。
使用带宽管理工具:带宽管理工具可以帮助控制网络流量,优化带宽分配和流量控制。通过合理管理带宽,可以提高网络的稳定性和性能。
这些是一些常见的TCP网络链路优化策略,可以根据具体的网络环境和需求选择适合的优化方法。同时,网络优化是一个复杂的过程,需要综合考虑多个因素,并进行实验和测试来评估优化效果。
3. 服务端处理优化
- 代码优化:对服务端代码进行优化,包括减少不必要的计算、避免重复操作、使用高效的算法和数据结构等。优化代码可以减少CPU和内存的使用,提高代码执行效率。
- 并发处理:使用多线程、多进程或异步处理等技术,充分利用服务器的多核处理能力,提高并发处理能力和吞吐量。合理地划分任务和资源,避免竞争条件和阻塞,提高系统的响应速度。
- 数据库优化:对数据库进行优化,包括合理设计数据库结构、使用索引、优化查询语句、缓存查询结果等。减少数据库的访问次数和查询时间,提高数据库的读写性能。
- 缓存优化:使用缓存技术将频繁访问的数据存储在内存中,减少对后端存储系统的访问。可以使用内存缓存(如Redis、Memcached)或分布式缓存(如CDN、分布式缓存系统)来提高数据访问速度。
- 负载均衡:使用负载均衡技术将请求分发到多个服务器上,避免单个服务器过载。可以使用硬件负载均衡器或软件负载均衡器来实现负载均衡,提高系统的可扩展性和稳定性。
- 异步处理:将耗时的操作(如文件读写、网络请求)转为异步处理,避免阻塞主线程。可以使用异步框架、消息队列等技术来实现异步处理,提高系统的并发能力和响应速度。
- 定期优化和监控:定期对服务端进行性能分析和监控,识别瓶颈和性能问题,并进行相应的优化。可以使用性能分析工具、日志分析工具等来帮助定位和解决问题。
这些是一些常见的服务端处理优化策略,可以根据具体的应用场景和需求选择适合的优化方法。同时,优化是一个持续的过程,需要不断地进行测试、评估和调整,以达到最佳的性能和用户体验。
4. 浏览器下载、解析、渲染页面优化
根据浏览器对页面的下载、解析、渲染过程,可以考虑一下的优化处理:
- 尽可能的精简HTML的代码和结构
- 尽可能的优化CSS文件和结构
- 一定要合理的放置JS代码,尽量不要使用内联的JS代码
- 将渲染首屏内容所需的关键CSS内联到HTML中,能使CSS更快速地下载。在HTML下载完成之后就能渲染了,页面渲染的时间提前,从而缩短首屏渲染时间;
- 延迟首屏不需要的图片加载,而优先加载首屏所需图片(offsetTop<clientHeight)
document.documentElement.clientHeight//获取屏幕可视区域的高度
element.offsetTop//获取元素相对于文档顶部的高度
因为JavaScript 会阻塞 DOM 生成,而样式文件又会阻塞 JavaScript 的执行,所以在实际的工程中需要重点关注 JavaScript 文件和样式表文件,使用不当会影响到页面性能的。
实际开发中优化首屏时间可用的技术手段
1. SSR渲染(服务端渲染)
SSR渲染是指服务器端渲染(Server-Side Rendering)的过程。传统的Web应用通常使用客户端渲染(Client-Side Rendering)的方式,即在浏览器中使用JavaScript动态生成页面内容。而SSR渲染则是将页面的初始渲染过程从客户端转移到服务器端。
在SSR渲染中,服务器端会在接收到客户端请求时,动态生成完整的HTML页面,并将其发送给客户端。这样,客户端在接收到HTML页面后,可以直接显示内容,而不需要等待客户端渲染完成。这种方式可以减少页面加载时间和白屏时间,提高用户体验。
SSR渲染的过程通常包括以下步骤:
客户端发送请求到服务器端。
服务器端接收到请求,并根据请求的路由信息确定要渲染的页面。
服务器端获取页面所需的数据,可以通过调用API、查询数据库等方式获取数据。
服务器端将获取到的数据注入到页面模板中,生成完整的HTML页面。
服务器端将生成的HTML页面发送给客户端。
客户端接收到HTML页面后,可以直接显示内容,而不需要等待客户端渲染完成。
客户端在接收到HTML页面后,可以继续执行JavaScript代码,处理交互逻辑和动态更新页面。
通过使用SSR渲染,可以提高页面的加载速度和用户体验,特别是对于首次访问页面的用户。然而,SSR渲染也会增加服务器的负载和响应时间,因此在实施SSR时需要进行适当的性能优化和资源管理。
2. 骨骼图,减少白屏时间,提高用户体感和留存
前端骨骼图(Skeleton Screen)是一种在页面加载过程中展示页面结构的技术。它通过在页面加载时先展示一个简单的骨架结构,然后逐渐填充内容,给用户一种页面正在加载的感觉。
前端骨骼图的主要作用是改善用户体验,减少页面加载时的白屏时间。以下是前端骨骼图的几个作用:
提示页面正在加载:前端骨骼图可以向用户传达页面正在加载的信息,让用户知道他们的操作已经被响应,并且页面即将显示。
- 减少白屏时间:通过展示页面的大致结构,前端骨骼图可以减少页面加载时的白屏时间。即使页面内容尚未完全加载,用户也可以看到页面的大致布局,给予他们一种页面正在加载的感觉。
- 提高用户满意度:前端骨骼图可以提高用户的满意度,因为他们可以立即看到页面的结构,而不需要等待页面完全加载。
- 提示用户交互元素位置:通过展示页面的骨架结构,前端骨骼图可以帮助用户快速定位和识别页面中的交互元素,提高用户的操作效率。
总的来说,前端骨骼图可以改善用户体验,减少页面加载时的不适感,提高用户满意度和页面的可用性。它是一种简单而有效的技术,可以在页面加载过程中给用户提供更好的反馈和体验。
3. 流式渲染(Streaming Rendering)
流式渲染(Streaming Rendering)是一种渲染页面的技术,它允许在服务器端逐步生成和发送页面内容给客户端,而不需要等待整个页面完全渲染完成。
传统的页面渲染方式是等待服务器端生成完整的HTML页面,然后一次性将整个页面发送给客户端。这种方式在页面内容较多或复杂时,会导致较长的白屏时间,用户需要等待较长时间才能看到页面内容。
而流式渲染则是在服务器端逐步生成页面内容,并将其逐段发送给客户端。这样,客户端可以在接收到部分内容后就开始渲染和显示页面,而不需要等待整个页面完全加载。
流式渲染的优势包括:
1.减少白屏时间:通过逐步发送页面内容,流式渲染可以减少白屏时间,让用户更快地看到页面内容。
2. 提高用户体验:用户可以在页面渲染的过程中进行交互,而不需要等待整个页面加载完成。这可以提高用户的满意度和页面的可用性。
2. 节省带宽和资源:流式渲染只需要发送页面的部分内容,可以减少网络传输的数据量,节省带宽和服务器资源。
3. 适应性更强:流式渲染可以根据网络状况和设备性能动态调整发送的内容,以提供更好的用户体验。
流式渲染的实现通常涉及使用适当的技术和框架,如服务器端渲染(SSR)、HTTP分块传输、WebSockets等。通过合理地划分页面内容和使用适当的渲染策略,可以实现流式渲染并提供更好的用户体验。
4. 如果一个列表有 100000 个数据,这个该怎么进行展示?
1. 将数据分页,利用分页的原理,每次服务器端只返回一定数目的数据,浏览器每次只对一部分进行加载。
2. 使用懒加载的方法,每次加载一部分数据,其余数据当需要使用时再去加载。
3. 虚拟列表,每次只渲染需要视口的部分
什么是虚拟列表?
虚拟列表(Virtual List)是一种优化大型列表渲染性能的技术。它通过只渲染可见区域内的列表项,而不是渲染整个列表,来减少页面渲染的开销。
虚拟列表的作用是提高列表的性能和用户体验,特别是在处理大量数据时。传统的列表渲染方式会将所有列表项都渲染到DOM中,无论其是否可见。这会导致页面加载缓慢、滚动卡顿等问题。而虚拟列表只渲染可见区域内的列表项,可以大大减少DOM操作和渲染开销,提高页面的响应速度和流畅度。
虚拟列表的用法通常涉及以下几个方面:
… 列表项的高度或平均高度,以便计算可见区域的范围。
… 列表项的数量,以便根据可见区域的范围计算需要渲染的列表项。
… 列表项的渲染方式,通常是通过动态创建和销毁DOM元素来实现。
… 列表项的滚动事件,以便在滚动时更新可见区域的范围,并触发列表项的渲染。
虚拟列表的具体实现方式可以根据使用的前端框架或库而有所不同。一些流行的前端框架,如React、Vue等,提供了相应的虚拟列表组件或插件,可以方便地实现虚拟列表的功能。
总的来说,虚拟列表是一种优化大型列表渲染性能的技术,通过只渲染可见区域内的列表项来提高页面的响应速度和流畅度。它在处理大量数据时非常有用,并且可以与各种前端框架和库结合使用。
虚拟列表原理
虚拟列表的原理是通过动态渲染可见区域内的列表项,而不是渲染整个列表。它基于以下两个核心概念:
- 可见区域范围计算:虚拟列表需要计算出当前可见区域的起始索引和结束索引。这可以通过列表容器的高度、列表项的高度(或平均高度)以及滚动位置来确定。根据这些参数,可以计算出当前可见区域内的列表项数量和索引范围。
- 动态渲染:根据可见区域的索引范围,虚拟列表只渲染可见区域内的列表项。当用户滚动列表时,虚拟列表会根据滚动位置和可见区域范围的变化,动态地创建或销毁列表项的DOM元素。这样,只有当前可见区域内的列表项才会被渲染到页面上,而不会渲染整个列表。
虚拟列表的实现通常涉及以下步骤:
- 初始化:计算可见区域的范围,并渲染初始的可见区域内的列表项。
- 滚动事件监听:监听列表容器的滚动事件,当滚动发生时,触发更新可见区域的操作。
- 更新可见区域:根据滚动位置和列表项的高度(或平均高度),计算出新的可见区域的范围。根据新的可见区域范围,动态地创建或销毁列表项的DOM元素。
- 渲染列表项:根据可见区域的索引范围,渲染可见区域内的列表项。这可以通过使用虚拟DOM或其他渲染技术来实现。
通过以上步骤,虚拟列表可以在滚动时动态地渲染可见区域内的列表项,从而提高页面的渲染性能和用户体验。
需要注意的是,虚拟列表的实现方式可能因使用的前端框架或库而有所不同。一些流行的前端框架,如React、Vue等,提供了相应的虚拟列表组件或插件,可以方便地实现虚拟列表的功能。
react实现虚拟列表(使用react库)
在React中,可以使用一些库或组件来实现虚拟列表的功能。其中,react-window 和 react-virtualized 是两个常用的库,它们提供了虚拟列表的组件和功能。
下面是使用 react-window 实现虚拟列表的基本步骤:
- 安装 react-window 库:
npm install react-window
- 导入所需的组件和函数:
import { FixedSizeList } from 'react-window';
- 创建列表项组件:
const ListItem = ({ index, style }) => {
// 根据索引获取列表项的数据
const item = data[index];
return (
<div style={style}>
{/* 渲染列表项的内容 */}
<p>{item.title}</p>
</div>
);
};
- 渲染虚拟列表组件:
const VirtualList = () => {
return (
<FixedSizeList
height={400} // 列表容器的高度
width={300} // 列表容器的宽度
itemCount={data.length} // 列表项的总数
itemSize={50} // 列表项的高度
>
{ListItem} // 列表项组件
</FixedSizeList>
);
};
在上述代码中,FixedSizeList 组件是 react-window 提供的虚拟列表组件,它接收一些必要的属性,如列表容器的高度、宽度,列表项的总数和高度等。ListItem 组件则是自定义的列表项组件,根据索引获取对应的数据,并渲染列表项的内容。
通过以上步骤,就可以使用 react-window 实现一个简单的虚拟列表。你可以根据实际需求进行进一步的定制和优化,例如添加滚动事件监听、动态加载数据等。
请注意,以上代码仅为示例,实际使用时需要根据具体情况进行适当的修改和调整。
原生Js实现虚拟列表
- 创建列表容器元素:
<div id="list-container" style="height: 400px; overflow-y: scroll;"></div>
- 获取列表容器和列表项的相关信息:
const listContainer = document.getElementById('list-container');
const itemHeight = 50; // 列表项的高度
const totalItems = 1000; // 列表项的总数
- 渲染可见区域内的列表项:
function renderVisibleItems() {
const scrollTop = listContainer.scrollTop;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + Math.ceil(listContainer.clientHeight / itemHeight), totalItems);
for (let i = startIndex; i < endIndex; i++) {
const listItem = document.createElement('div');
listItem.style.height = itemHeight + 'px';
listItem.textContent = 'Item ' + i;
listContainer.appendChild(listItem);
}
}
- 监听列表容器的滚动事件,并在滚动时更新可见区域的列表项:
listContainer.addEventListener('scroll', renderVisibleItems);
通过以上步骤,就可以实现一个简单的原生虚拟列表。在滚动时,会根据可见区域的范围动态地创建和销毁列表项,从而提高性能和用户体验。
需要注意的是,以上代码仅为示例,实际使用时需要根据具体情况进行适当的修改和调整。例如,可以添加数据加载、缓存机制等来优化列表的性能。
4. 使用数组分块技术,基本思路是为要处理的项目创建一个队列,然后设置定时器每过一段时间取出一部分数据,然后再使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。
5. 怎么进行站点内的图片性能优化?
在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以对图片进行优化势在必行。
如果页面启动时加载了几十张图片(甚至更多),而这些图片请求几乎是并发的,在 Chrome 浏览器,最多支持的并发请求次数是有限的,其他的请求会推入到队列中等待或者停滞不前,直到上轮请求完成后新的请求才会发出。所以相当一部分图片资源请求是需要排队等待时间的,过多的图片必然会影响页面的加载和展示
。
选择合适的图片格式
JPEG
JPEG 是由 Joint Photographic Experts Group 所开发出的一种图片。
它最大的特点是 有损压缩
。
这种高效的压缩算法使它成为了一种非常轻巧的图片格式。
另一方面,即使被称为“有损”压缩,JPG 的压缩方式仍然是一种高质量的压缩方式:当我们把图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。
此外,JPG 格式以 24 位存储单个图,可以呈现多达 1600 万种颜色,足以应对大多数场景下对色彩的要求,这一点决定了它压缩前后的质量损耗并不容易被我们人类的肉眼所察觉。
- 优点
- JPEG 格式的图片可以呈现数百万种颜色。所以每当网站需要呈现色彩丰富的图片,JPEG 总是最佳选择。
- 有损压缩,你可以通过压缩大大的减少图片的体积,一般图片用 60%级别比较合适,如果选择大于 75%的压缩等级,则会使图片有明显的质量下降。
- 无兼容性问题,所以开发者可以放心随意使用。
- 使用场景
- JPG 适用于呈现色彩丰富的图片,在我们日常开发中,JPEG 图片经常作为大的背景图、轮播图或 Banner 图出现。
- 但是有损压缩后的图片确实很容易露出马脚,当它处理矢量图形和 Logo 等线条感较强、颜色对比强烈的图像时,人为压缩的图片模糊会相当明显。
- JPEG 图像不支持透明度处理,透明图片可选择使用 PNG。
PNG
PNG(可移植网络图形格式)是由 W3C 开发的图片格式,是一种无损压缩
的高保真的图片格式。它同时支持 8 位和 24 位,这里都是二进制数的位数。按照我们前置知识里提到的对应关系,8 位的 PNG 最多支持 256 种颜色,而 24 位的可以呈现约 1600 万种颜色。
PNG 图片具有比 JPEG 更强的色彩表现力,对线条的处理更加细腻,对透明度有良好的支持。它弥补了上文我们提到的 JPEG 的局限性,唯一的缺点就是 体积太大
。
- 应用场景
- PNG 在处理线条和颜色对比度方面的优势,我们主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景等。
- 支持透明度处理,透明图片可选择使用 PNG
GIF
GIF 是一种最多支持 256 种颜色的 8 位无损图片格式。这个限制让 GIF 格式对于多颜色或者摄影图片的展示无能为力。
- 优点
- 支持 256 种颜色,文件体积通常都很小
- 支持透明
- 应用场景
- 支持动画,适合去展示一些无限循环的动画,比如图标、表情、广告栏等。
- 对于一些只有简单色彩的图片非常合适。
SVG
SVG(Scalable Vector Graphics)是一种基于XML的矢量图形格式,用于描述二维图形和图像。与位图图像(如JPEG、PNG)不同,SVG使用数学公式来描述图形,因此可以无损地缩放和放大而不会失真。
SVG图像由一系列的矢量图形元素组成,如路径(path)、矩形(rect)、圆形(circle)、椭圆(ellipse)、线条(line)、多边形(polygon)等。这些元素可以通过属性来定义其位置、大小、颜色、填充等样式。
SVG具有以下特点和优势:
- 矢量图形:SVG图像是基于数学公式的矢量图形,可以无损地缩放和放大而不会失真。这使得SVG非常适合在不同尺寸和分辨率的设备上显示,如高清屏幕、移动设备等。
- 小文件大小:由于SVG使用文本格式存储图形数据,相对于位图图像,SVG图像通常具有较小的文件大小,这有助于减少网络传输和页面加载时间。
可编辑性:SVG图像可以通过文本编辑器进行编辑和修改,可以添加、删除或修改图形元素和属性,使其更具交互性和动态性。 - 动画和交互性:SVG支持动画和交互效果,可以通过CSS和JavaScript来实现图形的动态变化、交互操作和响应。
- 可搜索和可索引:由于SVG图像是基于文本的,搜索引擎可以读取和索引其中的文本内容,从而提高网页的可搜索性和可访问性。
SVG广泛应用于Web开发、数据可视化、图标设计、动画制作等领域,它提供了一种灵活、可扩展和高质量的图形解决方案。
WebP
WebP 是一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式,派生自影像编码格式 VP8。它像 JPEG 一样对细节丰富的图片信手拈来,像 PNG 一样支持透明,像 GIF 一样可以显示动态图片,集多种图片文件格式的优点于一身。
WebP 最初在 2010 年发布,目标是减少文件大小,但达到和 JPEG 格式相同的图片质量,希望能够减少图片档在网络上的发送时间。根据 Google 较早的测试,WebP 的无损压缩比网络上找到的 PNG 档少了 45%的文件大小,即使这些 PNG 档在使用 pngcrush 和 PNGOUT 处理过,WebP 还是可以减少 28%的文件大小。
虽然 webP 有诸多优点,但是它不能完全替代 JPEG 和 PNG,因为浏览器对 WebP 支持并不普遍。特别是移动端 IOS 系统基本不支持。
AVIF
目前最新的图片格式是AVIF(AV1 Image File Format)。AVIF是一种基于AV1视频编解码器的图像格式,它使用先进的压缩算法来提供更高的图像质量和更小的文件大小。
AVIF格式具有以下优点:
- 更高的压缩效率:相比于JPEG和WebP等传统的图像格式,AVIF可以提供更高的压缩效率,从而在相同的图像质量下减小文件大小。
- 更好的图像质量:AVIF支持更高的色彩深度和更广的色域范围,可以提供更好的图像质量和更准确的颜色表示。
- 动态范围:AVIF支持高动态范围(HDR)图像,可以呈现更丰富的色彩和更高的对比度。
- 透明度支持:与WebP和PNG等格式类似,AVIF也支持透明度通道,可以实现图像的透明效果。
尽管AVIF具有许多优点,但由于它是一种相对较新的格式,目前还不被所有的浏览器和图像编辑软件广泛支持
。因此,在使用AVIF格式时,需要确保目标平台和设备支持该格式,或者提供备用的图像格式作为兼容性方案。
图片压缩
一张图片加载过程
1.发起请求:当浏览器解析HTML文档时,遇到标签或CSS中的background-image属性时,会发起对图片资源的请求。浏览器会根据src属性或url()函数中指定的图片路径,向服务器发送请求。
2.服务器响应:服务器接收到浏览器的请求后,会返回对应的图片资源。服务器的响应时间取决于网络连接的速度和服务器的性能。
3.下载图片:一旦浏览器接收到服务器的响应,它会开始下载图片。下载过程中,浏览器会根据网络状况和带宽限制,以及服务器的响应速度,逐步接收图片的数据。
4. 图片解码:当图片的数据完全下载到浏览器后,浏览器会对图片进行解码。解码过程将图像数据转换为像素,以便在屏幕上显示。
5.图片渲染:一旦图片解码完成,浏览器会将像素数据渲染到屏幕上的相应位置。图片的渲染过程包括将像素绘制到屏幕上的像素点,并根据CSS样式和布局进行相应的调整和显示。
加载完成:当图片渲染完成后,浏览器会触发load事件,表示图片加载完成。此时,可以通过JavaScript等方式对图片进行进一步的操作。
需要注意的是,图片加载过程中可能会受到网络状况、服务器响应速度、图片大小等因素的影响。优化图片加载的方法包括使用适当的图片格式、压缩图片大小、使用懒加载等技术,以提高页面加载速度和用户体验。
图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,有部分图片达到几百 kB,甚至 2M,直接导致了加载时间过长。所以对于体积过大的图片,在保持图片在可接受的清晰度范围内可适当对图片大小进行压缩。
图片压缩又分为有损压缩
和无损压缩
。
有损压缩
有损压缩指在压缩文件大小的过程中,损失了一部分图片的信息,也即降低了图片的质量(即图片被压糊了),并且这种损失是不可逆的。常见的有损压缩手段是按照一定的算法将临近的像素点进行合并。压缩算法不会对图片所有的数据进行编码压缩,而是在压缩的时候,去除了人眼无法识别的图片细节。因此有损压缩可以在同等图片质量的情况下大幅降低图片的体积。例如 jpg 格式的图片使用的就是有损压缩。
无损压缩
无损压缩指的是在压缩图片的过程中,图片的质量没有任何损耗。我们任何时候都可以从无损压缩过的图片中恢复出原来的信息。压缩算法对图片的所有的数据进行编码压缩,能在保证图片的质量的同时降低图片的体积。例如 png、gif 使用的就是无损压缩。
工具压缩
- tinypng 免费、批量、速度块
- 智图压缩 ,免费、批量、好用
- squoosh 在线图片压缩工具
- compressor 支持 JPG、PNG、SVG、GIF
webpack 压缩
工程化的项目可以在 webpack 里面配置 image-webpack-loader 进行图片压缩
- 安装依赖
npm install --save-dev image-webpack-loader
- 配置 webpack
module.exports = {
...
module: {
rules: [
{
test: /.(png|jpe?g|gif|svg)(?.*)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[hash:7].[ext]'
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 50,
},
optipng: {
enabled: true,
},
pngquant: {
quality: [0.5, 0.65],
speed: 4,
},
gifsicle: {
interlaced: false,
},
webp: { // 不支持WEBP就不要写这一项
quality: 75
},
},
},
],
},
],
},
}
至于要不要使用插件自动压缩就见仁见智了,因为有些 UI 和产品会说压缩出来的效果图片不是他们想要的。
使用雪碧图
雪碧图,CSS Sprites,国内也叫 CSS 精灵,是一种 CSS 图像合成技术,主要用于小图片显示。
浏览器请求资源的时候,同源域名请求资源的时候有最大并发限制,chrome 为 6 个,就比如你的页面上有 10 个相同 CDN 域名小图片,那么需要发起 10 次请求去拉取,分两次并发。第一次并发请求回来后,发起第二次并发。如果你把 10 个小图片合并为一张大图片的画,那么只用一次请求即可拉取下来 10 个小图片的资源。减少服务器压力,减少并发,减少请求次数。
优点:
把诸多小图片合成一张大图,利用 backround-position 属性值来确定图片呈现的位置,可以有效的较少请求个数,而且,而不影响开发体验,使用构建插件可以做到对开发者透明。适用于页面图片多且丰富的场景。
缺点:
生成的图片体积较大,减少请求个数同时也增加了图片大小,不合理拆分将不利于并行加载。
合成雪碧图
在 webpack 中,有相应的插件提供了自动合成雪碧图的功能并且可以自动生成对应的样式文件—— webpack-spritesmith,使用方法如下:
var path = require('path')
var SpritesmithPlugin = require('webpack-spritesmith')
module.exports = {
// ...
plugins: [
new SpritesmithPlugin({
src: {
cwd: path.resolve(__dirname, 'src/ico'),
glob: '*.png',
},
target: {
image: path.resolve(__dirname, 'src/spritesmith-generated/sprite.png'),
css: path.resolve(__dirname, 'src/spritesmith-generated/sprite.styl'),
},
apiOptions: {
cssImageRef: '~sprite.png',
},
}),
],
}
通过上面配置就能将 src/ico 目录下的所有 png 文件合成雪碧图,并且输出到对应目录,同时还可以生成对应的样式文件,样式文件的语法会根据你配置的样式文件的后缀动态生成。
使用 iconfont
iconfont(字体图标),即通过字体的方式展示图标,多用于渲染图标、简单图形、特殊字体等。
优点
- 像使用字体一样,设置大小、颜色及其他样式,不失真
- 轻量,易修改
- 有效减少 HTTP 请求次数
推荐使用阿里的字体图标库:iconfont
使用 base64 格式
**原理:**将图片转换为 base64 编码字符串 inline 到页面或 css 中。
优点
-
提升性能: 网页上的每一个图片,都是需要消耗一个 http 请求下载而来的, 图片的下载始终都要向服务器发出请求,要是图片的下载不用向服务器发出请求,base64 可以随着 HTML 的下载同时下载到本地.减少 https 请求。
-
加密: 让用户一眼看不出图片内容 , 只能看到编码。
-
方便引用: 在多个文件同时使用某些图片时, 可以把图片转为 base64 格式的文件, 把样式放在全局中, 比如 common.css, 以后在用的时候就可以直接加类名, 二不需要多层找文件路径, 会提升效率
但需要注意的是:如果图片较大,图片的色彩层次比较丰富,则不适合使用这种方式,因为该图片经过 base64 编码后的字符串非常大,会明显增大 HTML 页面的大小,从而影响加载速度。
base64 化最常见的就是在 url-loader 中使用。
module.exports = {
...
module: {
rules: [
{
test: /.(png|jpe?g|gif|svg)(?.*)?$/,
loader: 'url-loader',
options: {
limit: 10240,
name: utils.assetsPath('img/[name].[hash:7].[ext]'),
}
},
],
},
}
这样就能将项目中小于 10kb 的图片转化为 base64 应用到页面中
使用 css 代替图片
比如实现修饰效果,如半透明、边框、圆角、阴影、渐变等,在当前主流浏览器中都可以用 CSS 达成,这样能减少图片的请求,达到优化的目的。
缺点:
- 受限于 css 的浏览器的兼容性
- 对于较复杂的图案就无能为力了,写也麻烦,开发成本大
使用 CDN 图片
CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。
举个简单的例子:
以前买火车票大家都只能去火车站买,后来我们买火车票就可以在楼下的火车票代售点买了。
基本原理
CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。
基本思路
CND 的基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN 系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet 网络拥挤的状况,提高用户访问网站的响应速度。
CDN 的优势
- CDN 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
- 大部分请求在 CDN 边缘节点完成,CDN 起到了分流作用,减轻了源站的负载。
图片懒加载
懒加载是一种网页性能优化的方式,它能极大的提升用户体验。图片一直是影响网页性能的主要元凶,现在一张图片超过几兆已经是很经常的事了。如果每次进入页面就请求所有的图片资源,那么可能等图片加载出来用户也早就走了。所以进入页面的时候,只请求可视区域的图片资源。
总结出来就是:
- 减少资源的加载,页面启动只加载首屏的图片,这样能明显减少了服务器的压力和流量,也能够减小浏览器的负担。
- 防止并发加载的资源过多而阻塞 js 的加载,影响整个网站的启动,影响用户体验
- 浪费用户的流量,有些用户并不想全部看完,全部加载会耗费大量流量。
原理
图片懒加载的原理就是暂时不设置图片的 src 属性,而是将图片的 url 隐藏起来,比如先写在 data-src 里面,等当前图片是否到了可视区域再将图片真实的 url 放进 src 属性里面,从而实现图片的延迟加载。
function lazyload() {
let viewHeight = document.body.clientHeight //获取可视区高度
let imgs = document.querySelectorAll('img[data-src]')
imgs.forEach((item, index) => {
if (item.dataset.src === '') return
// 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
let rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
item.src = item.dataset.src
item.removeAttribute('data-src')
}
})
}
// 可以使用节流优化一下
window.addEventListener('scroll', lazyload)
通过上面例子的实现,我们要实现懒加载都需要去监听 scroll 事件,尽管我们可以通过函数节流的方式来阻止高频率的执行函数,但是我们还是需要去计算 scrollTop,offsetHeight 等属性,有没有简单的不需要计算这些属性的方式呢,答案是有的—IntersectionObserver
IntersectionObserver
IntersectionObserver是一个用于监测目标元素与其祖先元素或视窗(viewport)之间交叉状态的API。它提供了一种异步的、高性能的方法来观察元素是否进入或离开视窗,或者与其祖先元素发生交叉。
使用IntersectionObserver可以实现以下功能:
- 监测元素的可见性:可以观察元素是否进入或离开视窗,以便在元素可见或不可见时执行相应的操作。
- 实现懒加载:可以延迟加载图片、视频或其他资源,只有当它们进入视窗时才进行加载,从而提高页面加载速度和性能。
- 实现无限滚动:可以在滚动时动态加载更多的内容,当滚动到特定位置时触发加载新数据的操作。
- 监测元素与其祖先元素的交叉状态:可以观察元素与其祖先元素之间的交叉状态,例如元素是否完全进入祖先元素、部分进入或完全离开。
使用IntersectionObserver的优势包括:
- 异步执行:IntersectionObserver使用异步的方式进行观察,不会阻塞主线程,从而提高页面的响应性能。
- 高性能:IntersectionObserver使用浏览器内部的优化机制,可以高效地检测元素的交叉状态,减少不必要的计算和重复操作。
- 简化代码:相比于传统的滚动事件监听和计算元素位置的方式,IntersectionObserver提供了更简洁、易于使用的API,减少了开发者的工作量。
总之,IntersectionObserver是一个强大的API,可以帮助开发者实现各种与元素可见性和交叉状态相关的功能,提升用户体验和页面性能。
const imgs = document.querySelectorAll('img[data-src]')
const config = {
rootMargin: '0px',
threshold: 0,
}
let observer = new IntersectionObserver((entries, self) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let img = entry.target
let src = img.dataset.src
if (src) {
img.src = src
img.removeAttribute('data-src')
}
// 解除观察
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) => {
observer.observe(image)
})
图片预加载
图片预加载,是指在一些需要展示大量图片的网站,将图片提前加载到本地缓存中,从而提升用户体验。
常用的方式有两种,一种是隐藏在 css 的 background 的 url 属性里面,一种是通过 javascript 的 Image 对象设置实例对象的 src 属性实现图片的预加载。
1. 用 CSS 和 JavaScript 实现预加载
<!DOCTYPE html>
<html>
<head>
<style>
.preload-image {
display: none;
background-image: url('https://example.com/image.jpg');
}
</style>
<script>
function preloadImage(url) {
const img = new Image();
img.src = url;
}
// 预加载图片
preloadImage('https://example.com/image.jpg');
</script>
</head>
<body>
<div class="preload-image"></div>
</body>
</html>
在上面的代码中,我们首先在 标签中定义了一个 CSS 样式,使用 .preload-image 类来设置预加载图片的背景图。这个元素默认是隐藏的,因为我们使用了 display: none;。
然后,在
2. 使用 JavaScript 实现预加载
function preloadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('图片加载失败'));
img.src = url;
});
}
// 预加载图片的 URL 数组
const imageUrls = [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
'https://example.com/image3.jpg'
];
// 存储预加载的图片对象
const preloadedImages = [];
// 遍历图片 URL 数组,逐个进行预加载
imageUrls.forEach(url => {
preloadImage(url)
.then(img => {
preloadedImages.push(img);
console.log(`图片 ${url} 预加载完成`);
})
.catch(error => {
console.error(`图片 ${url} 预加载失败: ${error.message}`);
});
});
在上面的代码中,preloadImage 函数接受一个图片的 URL,返回一个 Promise 对象。在 Promise 的 resolve 回调中,我们将加载完成的图片对象传递给 resolve 函数;在 reject 回调中,我们将加载失败的错误信息传递给 reject 函数。
然后,我们定义了一个图片 URL 数组 imageUrls,以及一个用于存储预加载图片对象的数组 preloadedImages。
接下来,我们使用 forEach 方法遍历图片 URL 数组,并逐个调用 preloadImage 函数进行预加载。在 then 回调中,我们将加载完成的图片对象存储到 preloadedImages 数组中,并输出预加载完成的消息;在 catch 回调中,我们输出加载失败的消息。
这样,当所有图片都预加载完成后,preloadedImages 数组中将包含所有预加载的图片对象。
响应式图片加载
什么是响应式图片加载?其实就是在不同分辨率的设备上显示不同尺寸的图片,避免资源的浪费。
常用的方法就是 css3 的媒体查询(media query)。
@media screen and (min-width: 1200px) {
img {
background-image: url('1.png');
}
}
@media screen and (min-width: 992px) {
img {
background-image: url('2.png');
}
}
@media screen and (min-width: 768px) {
img {
background-image: url('3.png');
}
}
@media screen and (min-width: 480px) {
img {
background-image: url('4.png');
}
}
此外,还可以使用 HTML5 的 picture
属性进行响应式处理。方法如下:
- 创建 picture 标签。
- 放置多个 source 标签,以指定不同的图像文件名,进而根据不同的条件进行加载。
- 添加一个回退的元素
<picture>
<source srcset="src/img/l.png" media="(min-width: 1200px)" />
<source srcset="src/img/2.png" media="(min-width: 992px)" />
<source srcset="src/img/4.png" media="(min-width: 768px)" />
<img src="src/img/4.png" />
</picture>
需要注意的是:现在很多浏览器对于 picture 这个标签还不支持,使用的时候需要加以注意。
渐进式图片
渐进式图片的意思是在高画质图像加载完之前会先显示低画质版本。低画质版本由于画质低、压缩率高,尺寸很小,加载很快。在两者之间我们也可以根据需要显示不同画质的版本。
渐进式图片可以让用户产生图片加载变快的印象。用户不再盯着一片空白区域等待图片加载,而能看到图像变得越来越清晰,这样对用户体验也是友好的。
骨架屏技术也是类似的原理。
总结
- 选择合适的图片格式和压缩大图,可从根源上截图大图加载过慢的问题。
- 使用雪碧图,iconfont,base64,css 代替图片等可减少图片 http 请求,提高页面加载速度。
- 使用 CDN 图片可达到分流的效果,减少服务券压力。
- 图片懒加载,预加载,渐进式图片等可不同程度减少白屏时间,提高产品体验。