这篇文章是构建GitHub新首页的五部分系列的第三部分:
创建一个包含产品图片,动画和视频的页面,这些页面仍然可以快速加载并表现良好,这很棘手。在建立GitHub新首页的整个过程中,我们将Core Web Vitals用作我们的北极星和量尺之一。针对这些指标进行优化的方法有很多种,我们已经写过关于如何优化WebGL Globe的文章。我们将在这里深入探讨对我们产生最大整体性能影响的两种策略:制作高性能动画和提供完美的图像。
当您向下滚动GitHub主页时,我们会为某些元素设置动画,以引起您的注意:
传统上,一种典型的构建方法是侦听滚动事件,计算要跟踪的所有元素的可见性以及根据视口中元素的位置触发动画:
//监听旧式滚动事件(window.addEventListener(' scroll&#39 ;,()=> checkForVisibility)window.addEventListener(' resize&#39 ;,()=> checkForVisibility )function checkForVisibility(){animationElements.map(element => {const distTop = element.getBoundingClientRect()。top const distBottom = element.getBoundingClientRect()。bottom const distPercentTop = Math.round((distTop / window.innerHeight)* 100)const distPercentBottom = Math.round((distBottom / window.innerHeight)* 100)//基于此位置,对元素进行动画处理}}
这样的方法至少存在一个大问题:对getBoundingClientRect()的调用将触发重排,而利用此技术可能会很快造成性能瓶颈。
幸运的是,所有现代浏览器都支持IntersectionObservers,可以将它们设置为通知您元素在视口中的位置,而无需侦听滚动事件或调用getBoundingClientRect。只需几行代码即可设置IntersectionObserver,以跟踪元素是否显示在视口中,并使用每个条目的isIntersecting方法根据其状态触发动画:
//创建一个具有默认选项的相交观察器,//根据元素的可见性//触发类的打开/关闭//在视口中常量// animationObserver = new IntersectionObserver((entries,observer)=> {for(条目的常量条目) {entry.target.classList.toggle('内置动画&#39 ;, entry.isIntersecting)}}))///使用该IntersectionObserver观察某些元素的可见性/用于(querySelectorAll( ' .js-build-in')){animationObserver.observe(element);}
当我们移至IntersectionObservers进行动画制作时,我们还浏览了所有动画,并深入研究了优化动画的核心原则之一:仅对transform和opacity属性进行动画处理,因为这些属性对于浏览器来说更容易进行动画处理(通常计算上较便宜)。我们以为我们已经很好地遵循了这一原理,但是我们发现在某些情况下我们没有这样做,因为意外的特性正在渗入过渡中,并随着元素状态的变化而污染它们。
可能有人认为“仅动画转换和不透明度”原理的合理实现可能是在CSS中定义一个过渡,如下所示:
//请勿执行此操作。动画{opacity:0;转换:translateY(10px);过渡:* 0.6秒的缓动;}。animated:悬停{不透明度:0;转换:translateY(0);}
换句话说,我们只是显式地更改不透明度和变换,但我们定义的过渡是对所有更改后的属性进行动画处理。这些过渡可能会导致性能不佳,因为其他属性更改可能会污染过渡(例如,您可能具有会在悬停时更改文本颜色的全局样式),这可能会导致不必要的样式和布局计算。为避免这种动画污染,我们转向始终明确地仅定义不透明度并将其转换为可动画的形式:
//明确说明哪些可以设置动画(不可以设置动画)。animated{opacity:0;转换:translateY(10px); transition:不透明度0.6s缓和,transform 0.6s缓和;}。animated:hover {opacity:0;转换:translateY(0);}
当我们重建所有动画以通过IntersectionObservers触发并明确指定仅不透明度和可动画转换时,我们发现CPU使用率和样式重新计算急剧减少,从而帮助提高了“累积版式平移”得分:
如果您要通过视频元素来增强动画的效果,则可能需要做两件事:仅在视口中可见时播放视频,并在需要时延迟加载视频。令人遗憾的是,延迟加载属性不适用于视频,但是如果我们使用IntersectionObservers播放视口中出现的视频,则可以一次性获得这两个功能:
<!-HTML:内嵌,静音,不带自动播放功能的视频; preload-><视频循环静音播放器inline preload =" none" class =" js-viewport-aware-video" poster =" video-first-frame.jpg"> < source type =" video / mp4" src =" video.h264.mp4"< / video> // JS:播放在视口中可见的视频const videoObserver = new IntersectionObserver((entries,observer)=> {for(条目的const条目)entry.isIntersecting?video.play():video.pause();});用于(querySelectorAll(' .js-viewport-aware-video')的const元素){videoObserver .observe(element);}
连同将预加载设置为无,这种简单的观察器设置在每次页面加载时为我们节省了几兆字节。
我们使用许多不同的设备,屏幕和浏览器来访问网页,如果您想覆盖所有基础,显示图像的简单操作就变得越来越复杂。我们特殊的插图风格也恰好介于所有经典的JPG,PNG或SVG格式之间。以下面的示例为例,我们使用它来从主要叙述过渡到页脚:
要渲染此插图,理想情况下,我们需要PNG的透明度,但要将其与JPG的压缩效果结合起来,因为这样保存一个插图,因为PNG的大小为几兆字节。幸运的是,从iOS 14和macOS Big Sur开始,WebP在台式机和手机上的Safari中都受支持,这使浏览器支持提高了90%以上。 WebP实际上确实为我们提供了两全其美的优势:我们可以创建具有透明度的压缩,有损图像。对较旧浏览器的支持又如何呢?即使是在macOS Catalina上运行最新版本Safari的新Mac也无法渲染WebP图像,因此我们必须做一些事情。
这一挑战最终导致我们开发出一种晦涩难懂的解决方案:将两个JPG嵌入SVG(一个用于图像数据,一个用于遮罩),并作为base64数据嵌入-本质上是通过一个HTTP请求创建透明的JPG。看一下这张图片。下载它,打开它,然后检查它。是的,它是具有透明性的JPG,以base64编码,并包装在SVG中。
SVG规范的一部分是mask元素。有了它,您可以掩盖SVG的一部分。如果将SVG嵌入文档中,则可以将mask元素与image元素一起使用,以透明的方式呈现图像:
< svg viewBox =" 0 0 300 300"> < defs> < mask id =" mask"> <图像宽度=" 300" height =" 300" href =" mask.jpg">< / image> < / mask> < / defs> < image mask =" url(#mask)"宽度=" 300" height =" 300" href =" image.jpg"< / image>< / svg>
很好,但不能作为WebP的后备。由于这些图像的路径是动态的(请参见上例中的href),因此SVG需要嵌入文档中。相反,如果我们将此SVG保存在文件中并将其设置为常规img的src,则不会加载图片,并且看不到任何内容。
我们可以通过将图像数据作为base64嵌入SVG来解决此限制。网上有一些服务,您可以将图像转换为base64,但是如果您使用的是Mac,则默认情况下您的终端可以使用base64,您可以像这样使用它:
在文件内是您选择的图像,在文件外是文本文件,您将在其中保存base64数据。通过这种技术,我们可以将图像嵌入SVG内,并将SVG用作常规图像上的src。
这是我们用来构造页脚插图的两张图片,一张用于图像数据,另一张用于遮罩(黑色是完全透明的,白色是完全不透明的):
我们使用Terminal命令将遮罩和图像转换为base64,然后将数据粘贴到SVG中:
< svg xmlns =" http://www.w3.org/2000/svg" viewBox =" 0 2900 1494> < defs> < mask id =" mask"> <图像宽度=" 300" height =" 300" href =" data:image / png; base64,/ *您在base64中的图片* /”>< / image> < / mask> < / defs> < image mask =" url(#mask)"宽度=" 300" height =" 300" href =" data:image / jpeg; base64,/ *您在base64中的图片* /”>< / image>< / svg>
您可以保存该SVG并将其像任何常规图像一样使用。 然后,我们可以安全地将WebP与延迟加载和可靠的回退结合使用,从而适用于所有浏览器: 这种有点晦涩的SVG骇客在每次页面加载时为我们节省了数百KB,它使我们能够将最新技术用于支持它们的浏览器和操作系统。 我们正在整个公司范围内努力创建一个更快,更可靠的GitHub,这些是我们正在使用的一些技术。 我们还有很长的路要走,如果您想参加这个旅程,请查看我们的职业页面。