跳到主要内容

前端性能优化的几种方式

前端性能优化是提升网页加载速度和用户体验的关键方面。以下是几种常见的前端性能优化方式:

  1. 减少HTTP请求: 减少页面加载所需的HTTP请求次数是提高性能的重要方法。可以通过合并和压缩CSS和JavaScript文件、使用CSS Sprites(将多个图像组合成一个)和图像懒加载等方式来减少请求。

  2. 使用缓存: 通过使用浏览器缓存和服务器缓存,可以减少对服务器的请求次数,加快页面加载速度。使用适当的缓存头(Cache-Control、Expires)和ETag可以有效控制缓存策略。

  3. 优化图像: 图像通常是网页加载时间的主要因素之一。优化图像大小、选择合适的图像格式(如JPEG、PNG、WebP)以及使用图像压缩工具可以减小图像文件的大小,提高加载速度。

  4. 延迟加载: 对于一些不是首要显示的内容,可以使用延迟加载技术,如懒加载(Lazy Loading),只在用户滚动到可见区域时才加载该部分内容,以减少初始页面加载时间。

  5. 优化代码: 优化前端代码可以提高网页的执行效率。可以通过压缩和合并CSS和JavaScript文件、精简代码、避免重复计算和使用性能更高的算法等方式来改善代码性能。

  6. 异步加载: 将不影响页面首次渲染的JavaScript代码异步加载,可以避免阻塞页面的加载和渲染进程。使用asyncdefer属性或动态加载脚本的方式可以实现异步加载。

  7. 优化CSS: CSS文件的加载和解析也会影响页面性能。可以通过减少CSS选择器的复杂性、避免使用昂贵的CSS属性和使用嵌套较浅的选择器来优化CSS样式。

  8. 响应式设计: 使用响应式设计可以根据设备的屏幕大小和分辨率动态调整网页布局,提供更好的用户体验。通过使用CSS媒体查询和弹性布局技术,可以优化在不同设备上的显示效果。

  9. 减少重绘和重排: 避免频繁的DOM操作和样式变更,这会导致浏览器进行重绘和重排操作,影响性能。可以使用批量操作、使用transform替代topleft等方式来减少重绘和重排次数。

  10. 使用CDN: 使用内容分发网络(CDN)可以将资源分发到离用户更近的服务器上,减少网络延迟,加快资源加载速度。

综合利用上述优化方式,可以显著提升前端性能,加快网页加载速度,并提供更好的用户体验。

如何优化SPA应用的首屏加载速度慢的问题?

当SPA(单页应用)的首屏加载速度慢时,可以采取以下优化策略来改善性能:

  1. 代码分割和按需加载: 将应用代码进行分割,按需加载所需的代码块。这样可以减少首次加载所需的资源量,提高页面的响应速度。可以使用工具如Webpack的代码分割功能或使用动态导入(Dynamic Import)来实现按需加载。

  2. 预取和预加载资源: 使用预取(prefetching)和预加载(preloading)来获取未来页面所需的资源。通过在适当的时机预取或预加载关键资源,可以减少后续页面的加载时间。可以使用<link rel="prefetch"><link rel="preload">标签或使用相关的JavaScript库来实现预取和预加载操作。

  3. 优化图片加载: 图片是SPA应用中常见的加载性能瓶颈之一。可以采用以下措施来优化图片加载:

    • 使用适当的图像格式,如WebP、JPEG 2000等,以减小图像文件大小。
    • 压缩图像文件,以减少文件大小,可以使用工具如ImageOptim、TinyPNG等。
    • 使用懒加载(lazy loading)技术,只在视口内加载可见区域的图片,而不是一次性加载所有图片。
    • 使用CSS技术,如响应式图片、图像占位符等,来改善图像加载的性能。
  4. 使用缓存机制: 合理利用浏览器缓存,通过设置适当的缓存策略和HTTP头信息,使得一些不经常变化的资源能够被缓存起来,减少重复加载的次数。可以使用HTTP缓存头(如Cache-Control、Expires等)来控制浏览器缓存行为。

  5. 减少HTTP请求数量: 减少页面中的HTTP请求次数有助于提高页面加载速度。可以通过以下方式来减少请求数量:

    • 合并和压缩CSS和JavaScript文件,减少文件数量和大小。
    • 使用CSS Sprites来合并小图标和背景图,减少图片请求次数。
    • 使用字体图标或矢量图形代替某些图像,减少图像请求。
  6. 优化网络请求: 优化网络请求的性能也可以改善SPA应用的首屏加载速度。可以采取以下措施:

    • 使用CDN(内容分发网络)来加速静态资源的加载。
    • 启用Gzip压缩,减小传输的文件大小。
    • 使用HTTP/2协议,以支持多路复用和服务器推送等特性,提高请求的效率。
  7. 使用服务端渲染(SSR)或预渲染: 如果首屏加载速度仍然较慢,可以考虑使用服务端渲染(Server-Side Rendering)或预渲染(Prerendering)技术。这些技术可以在服务器端或构建过程中生成首屏的HTML,从而减少浏览器端的渲染时间。

通过结合以上优化策略,可以显著改善SPA应用的首屏加载速度,并提供更好的用户体验。根据具体情况,可以选择适合的优化策略来应对性能瓶颈。

首屏和白屏时间如何计算?

首屏时间(First Paint Time)和白屏时间(Blank Screen Time)是用于衡量网页加载性能的指标,计算方法如下:

  1. 白屏时间(Blank Screen Time): 白屏时间是指从用户发起请求到浏览器开始渲染内容之间的时间间隔。它表示用户在页面加载过程中所见到的空白页面的时间长度。

    白屏时间的计算可以通过浏览器提供的性能监测工具来实现,如使用 Performance API 中的 navigationStartresponseStart 两个时间戳之差,或者通过浏览器的开发者工具来查看相关指标。

  2. 首屏时间(First Paint Time): 首屏时间是指从用户发起请求到浏览器首次渲染出可见内容的时间间隔。它表示用户首次看到页面上有意义的内容所需要的时间。

    首屏时间的计算可以通过浏览器提供的性能监测工具来实现,如使用 Performance API 中的 navigationStartfirstPaint(或 firstContentfulPaint)两个时间戳之差,或者通过浏览器的开发者工具来查看相关指标。

需要注意的是,首屏时间和白屏时间都只是网页加载性能的指标之一,仅代表了用户在网页加载过程中的部分体验。其他指标如完全加载时间、可交互时间等也需要考虑,以全面评估网页的性能表现。此外,不同的浏览器和工具可能会有不同的实现和计算方法,因此在具体场景中,应根据浏览器和工具的要求来进行计算和分析。

Performance API有哪些常见的参数?

Performance API提供了一组用于测量和监测网页性能的参数。以下是其中一些常见的参数:

  1. navigationStart: 标记导航开始的时间戳,表示浏览器开始处理当前文档的时间。

  2. fetchStart: 标记开始获取文档的时间戳,表示浏览器开始获取网页资源的时间。

  3. domainLookupStart: 标记开始进行域名解析的时间戳,表示浏览器开始解析主机名的时间。

  4. domainLookupEnd: 标记完成域名解析的时间戳,表示浏览器完成解析主机名的时间。

  5. connectStart: 标记开始建立网络连接的时间戳,表示浏览器尝试建立与服务器的连接的时间。

  6. connectEnd: 标记完成网络连接的时间戳,表示浏览器与服务器建立连接完成的时间。

  7. requestStart: 标记开始发送请求的时间戳,表示浏览器开始向服务器发送请求的时间。

  8. responseStart: 标记开始接收响应的时间戳,表示浏览器开始接收服务器响应的时间。

  9. responseEnd: 标记完成接收响应的时间戳,表示浏览器完成接收服务器响应的时间。

  10. domLoading: 标记开始解析文档的时间戳,表示浏览器开始解析网页的时间。

  11. domInteractive: 标记文档可交互的时间戳,表示浏览器完成解析并可以开始处理DOM事件的时间。

  12. domContentLoadedEventStart: 标记DOMContentLoaded事件开始的时间戳,表示浏览器开始处理DOMContentLoaded事件的时间。

  13. domContentLoadedEventEnd: 标记DOMContentLoaded事件结束的时间戳,表示浏览器完成处理DOMContentLoaded事件的时间。

  14. domComplete: 标记文档解析完成的时间戳,表示浏览器完成解析整个文档的时间。

  15. loadEventStart: 标记页面加载事件开始的时间戳,表示浏览器开始处理load事件的时间。

  16. loadEventEnd: 标记页面加载事件结束的时间戳,表示浏览器完成处理load事件的时间。

这些参数可以通过 Performance API 中的 performance.timing 对象访问。它们提供了网页加载过程中各个阶段的时间戳,可以用于性能分析和优化。注意,不同的浏览器可能支持不同的参数,而且参数的精确性和可靠性也可能受到浏览器实现的影响。

性能分析的几个常见时间统计指标?

1. First Paint(FP)

First Paint的定义是渲染树首次转变为屏幕像素的过程,我们用FP time来表达首次渲染时间。在FP之前我们看见的屏幕是空白的,那么FP time也可理解为白屏时间。如何计算呢?

if (window.performance) {
let pf = window.performance;
let pfEntries = pf.getEntriesByType('paint')//这个第一次看到
let fp = pfEntries.find(each => each.name === 'first-paint')
console.log('first paint time: ', fp && fp.startTime)
}

2. First Contentful Paint(FCP):

FCP定义的是从页面加载到屏幕上首次有渲染内容的过程,这里的内容可以是文本、图像、svg元素和非白色canvas元素。在下图加载时间线中,图二是FCP的时间点: image 我们用FCP time来表达内容首次渲染时间。如何计算呢?

if (window.performance) {
let pf = window.performance;
let pfEntries = pf.getEntriesByType('paint')
let fp = pfEntries.find(each => each.name === 'first-contentful-paint')
console.log('first paint time: ', fp && fp.startTime)
}

需要区别于FP,总有FP time ≤ FCP time

3. First Meaningful Paint(FMP)

FMP定义的是从页面开始加载到渲染出主要内容的过程,这个“主要内容”的定义依赖于各浏览器中的实现细节,因此它并没有作为一个标准化的指标。在Chrome的Lighthouse面板中我们可以看到这个指标: image

4. Largest Contentful Paint(LCP)

FMP的范围不好界定,但LCP的范围是恒定的,它定义的是页面开始加载到渲染出(视口内)最大内容(文本或图像等)的过程。如下图加载时间线: image

image 第一个示例中,Instagram logo是视口中的最大内容,第二个示例中,绿色的文本是视口中的最大内容块。我们用LCP time表达最大内容渲染时间,如何计算呢?

new PerformanceObserver(list => {
let entries = list.getEntriesByType('largest-contentful-paint');
entries.forEach(item => {
console.log('largest contentful pain time: ', item.startTime)
})
}).observe({ entryTypes: ['largest-contentful-paint'] });

什么是首屏渲染?

我们这里定义的首屏是指页面无滚动的情况下,从开始加载到视窗第一屏内容渲染完成的过程,遵循上面几个概念的定义,我们可以称它为 last contentful paint,亦或first screen paint更贴切一些。在本文,我们就把首屏渲染时间叫做first screen paint time(FSP time),要如何来统计呢?

统计首屏渲染时间

先考虑最简单的场景:我们的页面是纯静态文本型的,即首屏里面没有图片,内容是静态文本。

我们要先解决一个问题:如何界定哪些元素是属于屏内的?

1. getBoundingClientRect

getBoundingClientRect用于获取某个元素相对于视窗的位置,理论上我们只要计算每一个元素的位置信息,结合视窗的高度信息,我们就能判断元素是否属于屏内。

但在真实情况下,一个页面dom的数量是很庞大的,大量的dom操作本身就会影响整个页面的性能!何况,getBoundingClientRect会引起页面重排(what forces reflow/layout),这并不是一个理想的方案;

2. IntersectionObserver + MutationObserver

IntersectionObserver通过启动一个观察器,以一种异步的方式检查目标元素是否出现于视窗(viewport)中,它返回的数据里面包含了两个重要的信息:

  • time:元素可见性发生变化的时间,一个高精度时间戳,单位毫秒;
  • intersectionRatio:目标元素的可见比例,介于0.0-1.0,为0时表示元素不可见,为1时表示元素完全可见。

接下来我们需要给每一个元素添加一个intersection观察器,MutationObserver可以帮助我们,它提供了监视dom树变更的能力,我们使用它监视document根节点的子树的变化,为新增的每一个子节点注册一个IntersectionObserver,参考如下代码:

// 注册可视性监听器
const isObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// 屏内元素
if (entry.intersectionRatio > 0) {
// 记录节点及其时间,这里也可以使用人工打点的方式:performance.now()
console.log(`${entry.target}: ${entry.time}`);
}
});
});

// 注册DOM树变更监听器
const muObserver = new MutationObserver((mutations) => {
if (!mutations) return;
mutations.forEach((mu) => {
if (!mu.addedNodes || !mu.addedNodes.length) return;
mu.addedNodes.forEach((ele) => {
// 只对元素节点进行监听
if (ele.nodeType === 1) {
// 添加可视性变化监听器
isObserver.observe(ele);
}
});
});
});

// 监听document的子树变化
muObserver.observe(document, {
childList: true,
subtree: true
});

更完整的代码参考:first-screen-paint

场景2:首屏包含图片资源,可能是图片元素或背景,需要计算加载最慢那张图片资源的耗时

问题1:图片资源是异步加载的,如何获取资源的请求耗时?

前文我们介绍了获取LCP time的方法,用类似的方式,我们也能获取图片资源的耗时,使用PerformanceObserver api监听资源的加载耗时,它返回的数据里面包含了几个重要的信息:

  • name:资源URL;
  • initiatorType:资源类型,取值可能是css|img|xmlhttprequest等;
  • startTime:请求开始时间,高精度时间戳值,单位毫秒;
  • responseEnd:请求响应返回的时间,高精度时间戳值,单位毫秒;
  • duration:responseEndstartTime的差值;
const pfObserver = new PerformanceObserver((list) => {
const entries = list.getEntriesByType('resource');
entries.forEach((item) => {
// 各种资源的耗时
// 首屏图片资源白名单:imgUrlWhiteList = []
console.log(`${item.name: ${item.duration}}`);
});
});
// 设定性能监听类别:资源
pfObserver.observe({ entryTypes: ['resource'] });

问题2:上面代码中我们监听了所有资源的请求,如何取出首屏的图片资源请求?

  1. 对于img标签的图片资源,我们可以在MutationObserver或者IntersectionObserver监听器中直接操作dom读取imgsrc或者data-src属性,把图片URL保存起来;
  2. 针对背景图片,我们使用getComputedStyle方法获取节点的样式表,并取出其background-image的值;

场景3:首屏内容是动态fetch的,甚至fetch的是图片资源,就如商城首页?

数据是动态fetch的,如果是纯文本数据,无图片资源。我们的DOM树变更监听器可以监听到数据返回之后的渲染情况,渲染过程会收集这些节点的可见性变化时间(这个时间肯定是在fetch数据返回时间点之后的);如果渲染的是图片资源,那么就进入了上一个处理图片资源的场景。

两个问题

1. 首屏内容还在加载中,用户触发了页面滚动?

页面滚动之后,第二屏的内容就会出现在视窗,原本属于首屏的内容(部分内容可能并未完成渲染)却没在视窗中。那么,按照如上的统计方式,就会统计到当前处于视窗内容的渲染时间,这可能就是一个“误差”。

我们需要一个共识在首屏内容完全渲染之前页面触发了滚动,说明页面已经是一个可交互的状态,这种情况下,我们认为,用户触发滚动时那一帧的内容,已经是用户和开发者双方都能接受的首屏内容。基于这个前提,我们的处理方式是:

在页面滚动时,加一个锁,停止监听后续内容的变更,以初次滚动的时间点为时间界线,统计在此时间点前发出的(依据startTime)所有资源的请求耗时和dom树节点的渲染时间;

2. 在场景3下,首屏内容未加载完,用户触发了页面滚动?

  • 这种情况下只能保底统计到fetch请求的响应结束时间;
  • 如果用户在响应之前触发了滚动,这时候数据渲染尚未开始,我们的程序无法捕捉到dom节点,那么也拿不到响应的图片资源,也就无法统计后续的渲染时间;
  • 如果用户是在数据返回之后,图片资源渲染之前触发的滚动,这种情况下由于能够捕捉dom树节点的渲染,理论上我们也能够获取响应图片资源的加载耗时;

测试一下

在本测试demo中,页面的主体内容是img元素,按照LCP(lagest contentful paint)的定义,LCP time会返回这张图片渲染的时间;而我们的首屏内容亦是这张图片,那么我们的FSP time应该基本等于LCP time,在下面截图中,也基本验证了这一点! image

最后,对于上面提到的几个问题,各位读者有任何看法也可在评论区留言~

https://github.com/lostvita/blog/issues/30#issue-892964765

常见的前端性能指标有哪些

前端性能指标主要关注的是用户体验和页面加载速度。以下是一些常见的前端性能指标:

  1. 首次内容绘制(First Contentful Paint,FCP):浏览器渲染来自DOM的第一个位图图像、文本或者SVG内容的时间。

  2. 首次有意义绘制(First Meaningful Paint,FMP):页面的主要内容渲染完成,用户开始可以与页面进行交互的时间。

  3. 可交互时间(Time to Interactive,TTI):页面可被用户完全交互(例如点击按钮或输入文本)的时间。

  4. 输入延迟(Input Delay):用户输入(例如点击按钮)到页面开始响应此操作的时间。

  5. 页面完全加载时间(Page Load Time):从用户开始导航到页面的全部内容(包括图片、CSS、JavaScript等)都加载完成的时间。

  6. 速度指数(Speed Index):页面内容加载的速度。速度指数越低,表示页面可视部分加载得越快。

  7. 累计布局偏移(Cumulative Layout Shift,CLS):页面在加载过程中,视觉元素移动的总距离。如果CLS得分较高,表示页面元素在加载时有较大的移动,可能会影响用户体验。

  8. 最大内容绘制(Largest Contentful Paint,LCP):页面最大的内容元素(如图片或文本块)渲染完成的时间。

以上这些指标可以通过浏览器的性能API或者使用Google的Lighthouse工具来获取。希望这个解释对你有所帮助!

了解 SPA 的懒加载么?

是的,我了解 SPA(Single-Page Application)的懒加载(Lazy Loading)技术。

懒加载是一种优化技术,用于延迟加载页面或组件所需的资源,直到它们真正需要被展示或使用。在 SPA 中,懒加载通常用于延迟加载页面的 JavaScript、CSS 和其他资源,以减少初始加载时间和提高用户体验。

SPA 的懒加载可以通过以下方式实现:

  1. 路由懒加载: SPA 通常使用路由来管理页面之间的切换。路由懒加载是指根据需要延迟加载特定页面的代码和相关资源。当用户导航到某个页面时,只有该页面所需的代码和资源才会被下载和执行,从而减少初始加载时间。常见的路由懒加载实现方式包括使用动态导入(Dynamic Import)或使用第三方库(如 React Router)提供的懒加载功能。

  2. 组件懒加载: 在 SPA 中,页面通常由多个组件组成。组件懒加载是指将某个组件的代码和相关资源延迟加载,直到该组件需要被渲染或使用。这可以通过使用动态导入(Dynamic Import)实现,将组件的导入语句放置在需要使用该组件的地方,从而在需要时才加载组件的代码和资源。

懒加载可以显著减少初始加载时间,尤其是当 SPA 包含大量页面或组件时。它可以提高页面的响应速度,并减少不必要的网络请求和资源加载,从而提升用户体验。然而,需要注意的是,懒加载可能会导致稍微延迟被加载的内容在首次访问时出现短暂的加载时间。

需要根据具体的技术栈和框架来实现懒加载,例如在 React 中可以使用 React.lazy() 和 Suspense 组件来实现组件的懒加载,而在 Vue.js 中可以使用异步组件来延迟加载组件。具体的实现方式会根据所用技术而有所不同,可以参考相应技术的文档和示例来了解如何在你的 SPA 中实现懒加载。

SEO 有哪些优化方案?

搜索引擎优化(Search Engine Optimization,SEO)是一系列技术和策略,旨在提升网站在搜索引擎中的排名,增加有机搜索流量。以下是一些常见的SEO优化方案:

  1. 关键词优化:确定目标关键词,将其合理地应用于网站的标题、元描述、URL、内容和标签等位置。关键词应与网站内容相关、具有一定的搜索量,并不过度堆砌。

  2. 内容优化:创建高质量、原创、有价值的内容,满足用户需求。确保内容结构清晰、易于阅读,并包含相关的标题标签、段落和列表等。使用适当的标题标签(H1、H2等)来突出重点。

  3. 网站结构优化:确保网站结构清晰、导航易用。使用合理的链接结构、内部链接和网站地图(Sitemap)来帮助搜索引擎理解和索引网站的内容。

  4. 外部链接建设:获取高质量的外部链接(Backlinks),这对于提高网站的权威性和排名非常重要。通过合作、内容创作、社交媒体等方式来吸引其他网站链接到你的网站。

  5. 移动友好性:优化网站以适应移动设备的浏览,确保网站在移动设备上加载速度快、布局合理,提供良好的用户体验。

  6. 网站速度优化:优化网站的加载速度,包括压缩图片、合并和压缩CSS和JavaScript文件、使用CDN等技术,以提供更快的页面加载时间。

  7. 社交媒体整合:将网站与社交媒体渠道整合,通过社交分享、评论和用户参与来增加网站的曝光度和流量。

  8. 定期更新和维护:保持网站内容的定期更新和维护,确保网站的可访问性、正常运行和安全性。

  9. 数据分析和优化:使用网站分析工具(如Google Analytics)来跟踪网站的流量、用户行为和转化率等指标,并根据数据进行优化和改进。

这些是一些常见的SEO优化方案,但请注意,搜索引擎算法和优化技术在不断发展变化,因此保持对SEO最新趋势和最佳实践的了解,并根据具体情况进行调整和优化是很重要的。

预渲染的原理是什么?

预渲染(Prerendering)是一种优化技术,用于提前生成页面的静态HTML内容,以便在用户访问时快速呈现。它的原理是在服务端或构建过程中,针对特定的页面进行预先渲染,生成对应的HTML文件,而不是在客户端动态生成内容。

下面是预渲染的基本原理:

  1. 静态页面生成:预渲染首先需要将页面的内容生成为静态HTML文件。这可以在构建过程中通过构建工具(如Webpack、Gulp等)或服务器端渲染框架(如Next.js、Nuxt.js等)来完成。

  2. 数据获取和注入:在预渲染过程中,可以模拟客户端的数据获取操作,从后端或其他数据源获取必要的数据,并将数据注入到生成的静态HTML中。这确保了预渲染后的页面包含了动态数据。

  3. 静态HTML存储:生成的静态HTML文件可以存储在服务器上的文件系统中,或者通过内容分发网络(CDN)进行缓存和分发,以确保快速的页面加载速度。

  4. 请求响应:当用户访问预渲染的页面时,服务器直接返回预先生成的静态HTML文件,而无需再进行服务器端渲染或客户端的数据获取和渲染过程。这大大减少了页面加载的延迟时间。

  5. 客户端交互:一旦页面加载完成,通常会有客户端的JavaScript代码接管页面,并处理用户的交互和动态内容的加载。这可以使页面在加载后变得更加动态和交互性。

预渲染的主要优点是提供了更快的初始加载速度和更好的搜索引擎优化(SEO)。由于预渲染生成了完整的静态HTML文件,搜索引擎能够更好地索引和理解页面内容,从而提高页面在搜索结果中的排名。同时,用户在访问预渲染页面时可以立即看到内容,无需等待动态内容的加载和渲染过程。

需要注意的是,预渲染并不适用于所有类型的网站或应用程序。它更适用于内容相对稳定的页面,而不是高度动态和个性化的页面。在某些情况下,客户端渲染(Client-side Rendering)或服务器端渲染(Server-side Rendering)可能更适合特定的需求。