提高在线商店的性能(案例研究)
已发表: 2022-03-10每个前端开发人员都在追逐同样的性能圣杯:Google Page Speed 的绿色分数。 工作做得好的明显迹象总是受到赞赏。 但是,就像寻找圣杯一样,您必须质疑这是否真的是您正在寻找的答案。 您的用户的真实性能以及网站在您使用它时的“感觉”不应该被打折,即使它会花费您一两点的页面速度(否则,我们都只会有一个搜索栏并且没有样式文本)。
我在一家小型数字代理机构工作,我的团队主要在大型企业网站和商店工作——页面速度有时会成为讨论的焦点,但通常到那时答案是需要大量重写才能真正实现任何目标,公司规模和项目结构的不幸副作用。
在其在线商店上与 Jewellerybox 合作对我们来说是一个可喜的变化。 该项目包括将商店软件升级到我们自己的开源系统,并从头开始重做商店的前端。 该设计是由一个设计和用户体验机构制作的,该机构还处理 HTML 原型(基于 Bootstrap 4)。 从那里,我们将它整合到模板中——这一次,我们有一个客户也痴迷于网站的性能。
对于发布,我们主要专注于推出新设计,但网站重新启动后,我们开始将注意力集中在将红色和橙色分数变为绿色。 这是一个充满艰难决定的多月旅程,有很多关于哪些优化值得追求的讨论。 今天,该网站速度更快,并且在各种展示和基准测试中排名很高。 在本文中,我将重点介绍我们所做的一些工作以及我们如何能够达到我们的速度。
在线商店有点不同
在我们详细介绍之前,让我们花一点时间来谈谈在线商店与许多其他网站的不同之处(如果您已经知道这一点,我们将在下一节中与您见面)。 当我们谈论电子商务网站时,您将拥有的主要页面是:
- 主页(和“内容”页面),
- 类别和搜索页面,
- 产品详细信息页面,
- 购物车和结帐(显然)。
在本文中,我们将重点关注前三个以及针对这些的性能调整。 结帐是它自己的野兽。 在那里,您将有很多额外的 JavaScript 和后端逻辑来计算价格,加上几个服务调用来获取适当的运输提供商和基于运送到的国家/地区的价格估计。
这显然是除了验证您需要记录帐单和送货地址的表单字段之外。 再加上支付提供商的插件,您自己就有了一些页面,一旦它们经过适当的测试和工作,就没有人愿意触摸它们。
当您想象在线商店时,您首先想到的是什么? 图片——大量的产品图片。 它们基本上无处不在,并将主导您的设计。 另外,您将希望展示许多产品以吸引人们向您购买 - 所以它是一个轮播。 可是等等! 人们会点击其中的产品吗? 我们可以通过在轮播上放置一些跟踪来找出答案。 如果我们跟踪它,我们可以优化它! 突然间,我们的页面上出现了由 AI 驱动的外部产品轮播。
问题是,轮播不会是您添加到页面以展示更多产品以吸引更多销售的最后一个速度惩罚元素。 当然,商店需要互动元素,无论是产品图像缩放、一些视频、今天发货截止日期的倒计时,还是与客户支持联系的聊天窗口。
当您直接将转化作为收入来衡量时,所有这些都非常重要。 另外,每隔几个月,团队中的某个人就会发现一些可以添加的很酷的新功能,因此复杂性和 JavaScript 开始累积,即使您一开始就打算保持精简。
虽然您通常可以缓存文章的整页,但许多商店页面和元素并非如此。 有些是特定于用户的,例如标题中的购物车或愿望清单,由于数据的个人性质,它们永远不应该被缓存。 此外,如果您有实物商品,您将处理实时库存:尤其是在圣诞节高峰期,您需要准确和最新的库存信息; 因此,您将需要一个更复杂的缓存策略,允许您缓存页面的某些部分,并在服务器端呈现期间将所有内容重新组合在一起。
但即使在规划阶段,陷阱也在等待。 在设计中——通常也是原型阶段——你将使用精心制作的产品名称和描述,长度几乎一致,以及理想的产品图像。 他们看起来棒极了! 唯一的问题? 实际上,产品信息的长度可能会非常不同,这可能会弄乱您的设计。 拥有数千种产品,您无法逐一检查。
因此,如果设计师或进行原型测试的人员使用非常短和非常长的字符串来确保设计仍然合适,这将有所帮助。 同样,信息在 HTML 中出现两次,一次用于桌面,一次用于移动,对于商店来说可能是一个大问题 - 特别是如果它是复杂信息,如产品详细信息、购物车或产品类别过滤器的方面页。 让它们保持同步是很难做到的——所以,请帮助其他开发人员,不要这样做。
另一件不应该是事后考虑并且应该从原型阶段开始就被纳入的事情是可访问性。 有几种工具可以帮助您了解一些基础知识,从为所有图像和图标提供具有功能的替代文本,到颜色对比,再到了解在何处(以及何时不使用)使用哪些 ARIA 属性。 从一开始就加入这一点比后来容易得多,它让每个人都可以享受你正在开发的网站。
这里有一个提示:如果您还没有看到人们使用屏幕阅读器或仅使用键盘进行导航,则可以在 YouTube 上轻松找到有关这方面的视频。 它将改变您对这些主题的理解。
回归业绩:为什么再次提升店铺业绩如此重要? 显而易见的答案是,您希望人们向您购买。 有几种方法可以影响这一点,您的网站速度很重要。 研究表明,每增加一秒的加载时间都会对转化率产生重大影响。 此外,页面速度是搜索和 Google Ads 的排名因素。 因此,提高绩效将对底线产生实实在在的影响。
我们做过的实际事情
一些性能瓶颈很容易识别,但彻底的改进是一段漫长的旅程,曲折多。 我们从所有常见的事情开始,例如重新检查资源的缓存,查看我们可以预取或异步加载的内容,确保我们使用的是 HTTP/2 和 TLSv1.3。 其中许多都包含在 CSS-Tricks 的有用概述中,Smashing Magazine 提供了一个很棒的 PDF 清单。
第一件事:所有页面上加载的东西
让我们谈谈资源,而不仅仅是 CSS 或 JavaScript(我们将在后面介绍)。 我们首先查看跨多个屏幕共享的内容,每个屏幕都会产生影响。 只有在那之后,我们才专注于页面类型和它们特有的问题。 一些常见的项目如下。
图标加载
与许多网站一样,我们在设计中使用了几个图标。 原型带有一些嵌入 SVG 符号的自定义图标。 这些作为一个大的svg
标记存储在页面的 HTML 头部中,每个图标都有一个符号,然后用作 HTML 正文中的链接svg
。 这具有在文档加载时使它们直接可供浏览器使用的良好效果,但显然浏览器无法为整个网站缓存它们。
所以我们决定将它们移动到一个外部 SVG 文件并预加载它。 此外,我们只包含了我们实际使用的图标。 这样,文件可以缓存在服务器和浏览器中,不需要解释多余的 SVG。 我们还支持在页面上使用 Font Awesome(我们通过 JavaScript 加载),但我们按需加载(添加一个检查任何<i>
标签的小脚本,然后加载 Font Awesome JavaScript 以获取任何 SVG它找到的图标)。 因为我们坚持使用自己的首屏图标,所以我们可以在 DOM 加载后运行整个脚本。
我们还在某些地方将表情符号用于彩色图标,这是我们没有人真正想到的,但我们的内容编辑 Daena 要求这样做,这是显示图标的好方法,对性能没有任何不利影响(唯一需要注意的是不同操作系统上的不同设计)。
字体加载
像在许多其他网站上一样,我们使用网络字体来满足我们的排版需求。 该设计要求正文中使用两种字体(两种粗细的Josefin Sans ),一种用于标题( Nixie One ),一种用于特殊样式的文本( Moonstone Regular )。 从一开始,我们将它们存储在本地,使用内容交付网络 (CDN) 来提高性能,但在阅读了 Simon Hearne 关于避免字体加载时布局变化的精彩文章后,我们尝试删除粗体版本并使用常规版本。
在我们的测试中,视觉差异是如此之小,以至于我们的测试人员都无法在不同时看到两者的情况下分辨出来。 所以,我们去掉了字体粗细。 在撰写本文并为本节准备视觉辅助材料时,我们偶然发现 Mac 上基于 Chromium 的浏览器与高分辨率屏幕上基于 WebKit 的浏览器之间存在较大差异(耶,复杂!)。 这引发了关于我们应该做什么的另一轮讨论。
经过一番反复,我们选择保持假粗体并使用-webkit-text-stroke: 0.3px
来帮助那些特定的浏览器。 与使用实际单独字体粗细的区别很小,但对于我们的用例来说还不够,我们几乎不使用粗体字体,一次只使用几个单词(对不起,字体爱好者)。
此外,一些产品可以通过雕刻进行个性化。 这些雕刻可以用多种字体完成,对于某些字体,我们提供应用字体的预览。 对于这些,我们在下拉字体选择器中选择字体时按需下载字体。 下拉列表中的预览是字体外观的示例图像。 这使我们不必下载 10 个或更多额外的字体文件。
旧版支持
有一天,CSS-Tricks 发表了一篇关于“如何在 2021 年实现 Favicon”的文章,让我大吃一惊。 我们使用了世界上所有尺寸的触摸图标——这篇文章让我重新评估了我们真正需要的东西,并向我展示了有时几年前的真实情况可能不再需要了。 根据这篇文章,我们将网站图标和触摸图标列表限制为推荐版本。
同样,我们还将仅作为WOFF 版本的字体转换为小得多的 WOFF2 ,我们决定为字体提供 WOFF2(保留 WOFF 作为后备)。 我们清除了不再需要的 CSS 指令。
延迟和按需加载
几个指标集中在用户可以与页面交互的时间上。 逻辑表明,要加载的元素更少意味着将更快地达到这一点。 考虑到这一点,重要的是要问自己页面的哪些部分是必不可少的,以及用户以后才需要哪些部分。 我们对此进行了很多辩论和反复试验。
网络活动的瀑布在这里有很大帮助,但对用户流的思考也是如此。 例如,缩放后的产品图片可以在用户第一次与产品图片交互时加载,而页脚中的图片通常不会显示在首屏,可以稍后加载。 如果您担心速度变慢,您仍然可以使用预取资源。
我们很早就注意到的一件事是聊天客户端的巨大影响。 仅 JavaScript 就超过 500 KB,虽然很遗憾我没有图表,但加载速度也很慢。 尽管 JavaScript 被设置为异步加载,Google 还是将其包含在交互时间测量中。
我们无法完全追踪为什么会出现这种情况,但在它和它的庞大规模之间,我们开始寻找替代方案,而不是试图修复我们控制有限的东西。 我们说服Jewellerybox尝试使用自托管的开源聊天小部件,这让我们可以更好地控制它的加载方式,而且体积也更小。 为了进一步改进它,我们最初只加载聊天的图标; 当您单击打开它时,其余部分会被加载。
隐形的第三方轮播
Jewellerybox 想尝试一种第三方服务,该服务使用 AI 来改善多页轮播中的产品个性化。 我们决定为此从后端调用它的 API,以便我们可以更好地控制加载的内容(没有大的 JavaScript 依赖项)以及等待服务响应的时间(定义了一些后备)。 因此,轮播以与非个性化轮播相同的方式加载,并且也可以使用唯一的缓存键进行缓存。
但是有一个缺点:这意味着服务器端的初始页面呈现可能会更慢,除非缓存。 出于这个原因,我们目前正在研究在页面加载并首先呈现占位符后注入结果的替代方法。
第二步:优化 JavaScript — 与外部敌人的艰苦战斗
轮播将我们带到了我们关注的第二大领域:JavaScript。 我们审核了我们拥有的 JavaScript,其中大部分来自用于不同任务的库,几乎没有自定义代码。 我们优化了我们自己编写的代码,但显然,如果它只是整个代码的一小部分,那么您可以做的只有这么多——最大的收获在于库。
优化库中的东西或取出不需要的部分很可能是愚蠢的差事。 您并不真正知道为什么有些部件在那里,并且如果没有大量的手动工作,您将永远无法再次升级库。 考虑到这一点,我们退后一步,查看了我们使用哪些库以及我们需要它们的用途,并为每个库调查是否存在更小或更快的替代方案,同时也满足我们的需求。
在几种情况下,有! 例如,我们决定用 GliderJS 替换 Slick 滑块库,它具有更少的功能,但我们需要的所有功能,而且加载速度更快,并且具有更现代的 CSS 支持! 此外,我们从主要的 JavaScript 文件中取出了许多自包含的库,并开始按需加载它们。
因为我们使用的是 Bootstrap 4,所以我们仍然在项目中包含 jQuery,但正在努力用本机实现替换所有内容。 对于 Bootstrap 本身,在 GitHub 上有一个 bootstrap.native 版本,没有我们现在使用的 jQuery 依赖项。 它体积更小,运行速度更快。 另外,我们生成了两个版本的主 JavaScript 文件:一个不包含 polyfill,另一个包含它们,当浏览器需要它们时,我们会与它们交换版本,使我们能够为大多数人提供流线型的主版本。 我们测试了“按需填充”服务,但性能没有达到我们的预期。
关于 jQuery 主题的最后一件事。 最初,我们从服务器加载它。 通过 Google CDN 加载测试系统时,我们看到了性能改进,但 Page Speed Insights 抱怨性能问题(我想知道谁能解决这个问题),所以我们再次测试自己托管它,并且在生产中它实际上更快,因为我们使用的 CDN。
经验教训:测试环境不是生产环境,对一个环境的修复可能不适用于另一个环境。
第三:图像——格式、尺寸和所有爵士乐
图像是构成在线商店的重要组成部分。 一个页面通常会有几十张图片,甚至在我们计算不同设备的不同版本之前。 珠宝盒网站已经存在将近 10 年了,大部分时间都可以买到许多产品,因此原始产品图片的尺寸和样式并不统一,产品照片的数量也可能会有所不同。
理想情况下,我们希望以现代格式为不同的视图大小和显示密度提供响应式图像,但是任何需求的变化都意味着需要完成大量的转换工作。 因此,我们目前使用优化尺寸的产品图片,但我们没有针对它们的响应式图片。 更新是在路线图上,但不是微不足道的。 内容页面提供了更大的灵活性,我们在那里生成和使用不同的大小,包括 WebP 和后备格式。
拥有如此多的图像会增加初始有效负载的权重。 因此,何时以及如何加载图像成为一个巨大的话题。 延迟加载听起来像是解决方案,但如果普遍应用它可以减慢最初可见的图像,而不是直接加载它们(或者至少对用户来说是这样的感觉)。 出于这个原因,我们选择了直接加载前几个和延迟加载其余部分的组合,使用原生延迟加载和脚本的组合。
对于网站徽标,我们使用 SVG 文件,为此我们从客户端获得了初始版本。 徽标是一种错综复杂的字体,其中缺少部分字母,因为它们是手工完成的不完美印刷。 在大尺寸中,您需要显示细节,但在网站上我们从不使用超过 150 x 30 像素的尺寸。 原始文件大小为 192 KB,不算大,但也不算超小。 我们决定使用 SVG 并减少其中的细节,我们最终得到了一个 40 KB 大小的解压缩版本。 我们使用的显示尺寸没有视觉差异。
最后但绝对不是最不重要的:CSS
关键 CSS
CSS 在 Google 的 Chrome 用户体验报告 (CrUX) 中占有重要地位,并且在 Google Page Speed Insights 报告和建议中也占有重要地位。 我们做的第一件事是定义一些关键的 CSS,我们直接在 HTML 中加载它们,以便浏览器尽快使用它——这是对抗内容布局转换 (CLS) 的主要武器。 我们选择了基于原型页面的关键 CSS的自动提取和我们可以定义要提取的类名的机制(包括所有子规则)的组合。 我们对添加到相应页面类型的一般样式、产品页面样式和类别样式分别执行此操作。
我们从中学到的一点是,这导致了一些错误,那就是我们必须小心 CSS 的顺序不会因此而改变。 在编写代码的不同人之间、稍后在文件中添加覆盖的人以及提取内容的自动工具之间,它可能会变得混乱。
针对 CLS 的显式维度
对我来说,CLS 是谷歌从它的帽子里抽出来的东西,现在我们都需要处理它,并把我们的集体头脑围绕在它周围。 以前,我们可以简单地让容器从其中的元素中获取它们的大小,现在这些元素的加载可能会影响盒子的大小。 考虑到这一点,我们使用了开发人员工具中的“性能”选项卡和超级有用的 Layout Shift GIF 生成器来查看导致 CLS 的元素。 从那里,我们不仅查看了元素本身,还查看了它们的父元素,并分析了会对布局产生影响的 CSS 属性。 有时我们很幸运——例如,标志只需要在移动设备上设置一个明确的尺寸来防止布局变化——但其他时候,斗争是真实的。
专业提示:有时变化不是由明显的元素引起的,而是由它之前的元素引起的。 要确定可能的罪魁祸首,请关注大小和间距发生变化的属性。 要问自己的基本问题是:什么可能导致这个块移动?
因为页面上有这么多图像,使用 CLS 让它们正确运行也需要我们做一些工作。 Barry Pollard 在他的文章“设置图像的高度和宽度再次重要”中正确地提醒了我们这一点。 我们花了很多时间为每种情况下的图像找出正确的宽度和高度值(加上纵横比),以便再次将它们添加到 HTML 中。 结果,图像的布局不再发生变化,因为浏览器会提前获取信息。
神秘的 CLS 分数案例
在删除了页面顶部附近的许多重大 CLS 问题后,我们遇到了障碍。 有时(并非总是)在查看 Page Speed 或 Lighthouse 时,我们的 CLS 得分超过 0.3,但从未出现在“性能”选项卡中。 Layout Shift GIF Generator有时会显示它,但看起来整个页面容器都在移动。
启用网络和 CPU 限制后,我们终于在屏幕截图中看到了! 由于其中的元素,移动设备上的标题高度增加了 2 个像素。 因为标题在移动设备上是固定高度的,所以我们采用了简单的修复并为其添加了显式高度 - 案例关闭。 但这花费了我们很大的精力,这表明这里的工具仍然很不精确。
这是行不通的——让我们重做吧!
众所周知,移动端的 Page Speed 分数比桌面端的分数要高得多,其中一个对我们特别不利的领域是产品页面。 CLS 分数高居榜首,页面也存在性能问题(几个轮播、选项卡和不可缓存的元素会这样做)。 更糟糕的是,页面的布局意味着一些信息被打乱或添加了两次。
在桌面上,我们基本上有两列内容:
- A 列:产品照片轮播,有时后面是博客引用,然后是带有产品信息的选项卡式布局。
- B 列:产品名称、价格、描述和“添加到购物车”按钮。
- C行:同类产品的产品轮播。
但是,在移动设备上,产品照片轮播需要先出现,然后是 B 列,然后是 A 列的选项卡式布局。因此,某些信息在 HTML 中重复,由display: none
控制,并且订单正在使用flex: order
属性切换。 它确实有效,但对 CLS 分数不利,因为基本上所有内容都需要重新排序。
我决定在 CodePen 中进行一个简单的实验:我能否通过重新考虑 HTML 并使用display: grid
而不是 flexbox 在桌面和移动设备上实现相同的基本框布局,这是否允许我根据需要简单地重新排列网格区域? 长话短说,它奏效了,它解决了 CLS,它还有一个额外的好处,即现在产品名称在 HTML 中出现的时间比以前快得多——增加了 SEO 胜利!
破解 CLS 的轮播
“轮播”这个词已经出现了好几次——而且有充分的理由。 我们不仅更改了我们使用的轮播库(并更改了其中图像的加载行为),我们还必须为 CLS 处理它,因为我们有几个页面上的轮播位于首屏,因此,可能会对速度分数产生很大影响。
我们先是稍后加载轮播以减少 time-to-interactive ,但这会导致明显的延迟,直到 JavaScript 触发并且幻灯片从彼此下方移动到一排。 我们尝试了很多方法来编写 CSS 以防止这种情况发生并将所有内容保持在一行,包括隐藏整个轮播直到它完成加载。 没有什么能提供我们作为用户访问商店时希望看到的那种解决方案。
抱歉这么短的咆哮,但确实,产品和类别轮播是响应式商店中灵活元素的完美风暴:图像可能不是通用高度,产品名称可能跨越多行,您可能有也可能没有标签。 基本上,它归结为:行没有固定的高度是可能的,而且你也不知道宽度。 娱乐时间。
最后,我们决定将所有幻灯片(除了第一个)设置为visibility: hidden
直到轮播完成加载,此时我们向轮播添加一个类以将所有幻灯片更改为再次可见。 这解决了它首先占用额外高度的问题。
此外,我们最初为非包装弹性盒中的幻灯片设置了flex-shrink: 0
和flex-base: 340px
。 这使它们位于一条线上,并为幻灯片提供了一个近似的初始宽度。 随着这个谜题的解决——是的,这听起来很让人头疼——我们添加了一些修复程序来为点和箭头留出空间。 有了这个,旋转木马几乎没有 CLS 了!
事后看来是20 ⁄ 20
最后,是几个月来的许多小改动提高了我们的分数,但我们还没有完成。 我们主要与两个人一起进行前端改进,而团队的其他人则专注于改进后端。 虽然这样可能会慢一点,但它确保没有重叠,并且可以清楚地归因于分数的差异。 一些很有帮助的资源是 Smashing Magazine 上关于该杂志自身改进的精彩文章。
在某些时候,你应该尝试的事情变得不明显,因为你认为它们不应该产生巨大的影响,但之后的某个时候你意识到它们确实如此。 不仅如此,这个项目再次告诉我们的是,从一开始就考虑性能和指标是多么重要,从设想设计和编码原型到模板中的实现。 早期被忽视的小事情可能会增加你以后必须爬上才能撤消的大山。
以下是我们学到的一些关键方面:
- 优化 JavaScript 不如按需加载有效;
- 优化 CSS 似乎比优化 JavaScript 获得更多分数;
- 使用 CLS 编写 CSS 类并牢记关键 CSS 的提取;
- 查找 CLS 问题的工具还不完善。 跳出框框思考并检查几个工具;
- 评估您集成的每个第三方服务的文件大小和性能时间。 如果可能的话,推迟整合任何会减慢一切的东西;
- 定期重新测试您的页面以了解 CrUX 更改(尤其是 CLS);
- 定期检查是否仍需要所有旧支持条目。
我们的改进清单上还有一些东西需要改进:
- 我们在主文件中仍然有很多未使用的 CSS 可以删除;
- 我们想完全删除 jQuery。 这将意味着重写我们的部分代码,尤其是在结帐区域;
- 需要对如何包含外部滑块进行更多的实验;
- 我们的移动点分数可能会更好。 尤其是移动方面需要进一步的工作;
- 所有产品图片都需要添加响应式图片;
- 我们将专门检查内容页面以了解他们可能需要的改进,尤其是在 CLS 方面;
- 使用 Bootstrap 的折叠插件的元素将被替换为原生 HTML
details
标签; - DOM 大小需要减小;
- 我们将整合第三方服务以获得更快更好的搜索结果。 这将伴随我们需要集成的大量 JavaScript 依赖项;
- 我们将通过查看自动化工具以及自己使用屏幕阅读器和键盘导航运行一些测试来改进可访问性。
更多资源
- “DevTools 调试技巧和快捷方式(Chrome、Firefox、Edge)”,Vitaly Friedman,Smashing Magazine
- “我最近收藏和阅读的一些性能博客文章,” Chris Coyier,CSS-Tricks
- “衡量核心网络生命力的深入指南”,Barry Pollard,Smashing Magazine
- “从语义 CSS 到 Tailwind:重构 Netlify UI 代码库”,Netlify 的 Charlie Gerard 和 Leslie Cohn-Wein
- “CSS 审计工具”,Iris Lješnjanin,Smashing Magazine
- “今天你可以用 CSS 做的事情”,Andy Bell,Smashing Magazine
- “如何提高 CSS 性能”,Calibre 的 Milica Mihajlija
- “移动性能不平等差距,2021 年,”亚历克斯·罗素
- “在 2021 年最大限度地优化 Web 的图像加载,” Malte Ubl
- “谦虚的
<img>
元素和核心网络生命力”,Addy Osmani,Smashing Magazine