当前位置: 首页 > 科技观察

浅谈Web应用中的图片优化技巧与反思

时间:2023-03-19 14:33:18 科技观察

本文将一起探讨在Web应用中可以进行什么样的图片优化,并对一些“负优化”的方法进行反思。1、为什么要优化图片?对于大多数前端工程师来说,图片就是UI设计师(或者你自己)剪下来的图片,你要做的就是把图片扔到项目中,然后呈现在链接页面上,而我们往往关注关于项目的打包优化和构建,如何分包,如何提取第三方库……有时候我们忘记了图片是一个网站最大的头像块加载资源(见下图),虽然图片加载可以不妨碍页面渲染,但是优化图片绝对可以让网站的体验更上一层楼。2、从图片大小优化压缩可以使用统一的压缩工具——imagemin,它是一个可以集成多个压缩库的工具,支持jpg、png、webp等格式的图片压缩,如pngquant、mozjpeg等,作为测试,我们可以直接安装imagemin-pngquant来试试png图片的压缩:PNG压缩:npminstallimageminnpminstallimagemin-pngquant这里先安装imagemin库,再安装对应的png压缩库。constimagemin=require('imagemin');constimageminPngquant=require('imagemin-pngquant');(async()=>{awaitimagemin(['images/*.png'],'build/images',{插件:[imageminPngquant({quality:'65-80'})]});console.log('Imagesoptimized');})();我们可以在quailty中判断压缩比,65-80好像是压缩比和质量的结合,为了达到一个平衡的值,腾讯AlloyTeam出品的gka图像处理工具也是用的imagemin库,他们也是用的65-默认为80选项:gka代码使用它来压缩png图像,让我们看看它是如何工作的:这是压缩前:这是压缩:肉眼几乎无法区分,但实际上缩小了77%!读者可自行保存图片进行对比。JPG/JPEG压缩和渐进式图像压缩jpg/jpeg图像类似于png,imagemin提供了两个插件:jpegtrain和mozjpeg供我们使用。一般我们选择mozjpeg,它有更丰富的压缩选项:],'build/images',{use:[imageminMozjpeg({quality:65,progressive:true})]});console.log('Imagesoptimized');})();注意我们使用了progressive:true选项,这个可以将图片转换为渐进图片,关于渐进图片,它允许在加载照片时,如果网络速度比较慢,首先显示质量较差的照片,如模糊的小马赛克,然后慢慢变清晰的照片:相比之下,非渐进式图片(BaselineJPEG)会老老实实从头到尾加载:张新旭的这篇文章可以帮助你更好地理解两者两者的区别:Progressivejpeg(progressivejpeg)图片及其相关性简单来说,progressive图片从一开始就决定大小,不像Baseline图片,从上到下不断加载,导致多次回流。然而,渐进式图像需要消耗CPU进行多次计算和渲染,这是它的主要缺点。当然interlacedpng也可以实现相应的效果,但是目前pngquant没有实现转换功能,但是可以在ps导出png的时候设置为interlaced。它在实际项目中是如何工作的?在实际项目中,总不能从UI中拖出一张图片,直接运行压缩代码吧?幸运的是,imagemin有对应的webpack插件。在到处使用webpack的今天,我们可以轻松实现批量压缩:{plugins:[imageminMozjpeg({quality:100,progressive:true})]})]}然后在webpack的配置文件中,导入你需要的插件,使用完全相同的方法。具体可以参考github文档imagemin-webpack-plugin。3.按需加载图片,减轻请求压力。按需加载图片是一个常见的话题。传统的方法是监听页面的滚动位置,当条件满足时再加载资源。让我们看看还有哪些方法可以用来完成这个需要加载。使用强大的IntersectionObserverIntersectionObserver为我们提供了一个能力:它可以用来监控一个元素是否进入了设备的可见区域,也就是说:我们等待图片元素进入可见区域之后再决定是否加载它,毕竟用户在没有看到图片之前,并不关心是否加载过。这是Chrome51最先提出并支持的API,2019年的今天,各大浏览器的支持都有所提升(IE除外,全线崩盘):话不多说,我们上代码:首先,假设我们有图像列表。我们暂时不设置它们的src属性,而是使用data-src代替:

  • li>
  • 这将导致图片加载失败,这当然不是我们的目的。我们要做的是,当IntersectionObserver监测到图片元素进入可见区域时,将data-src“返回”给src属性,这样我们就可以加载图片了:constobserver=newIntersectionObserver(function(changes){changes.forEach(function(element,index){//当这个值大于0时,说明满足我们的加载条件,这个值可以通过rootMargin手动设置if(element.intersectionRatio>0){//给up监控,防止性能浪费,加载图片。observer.unobserve(element.target);elementelement.target.src=element.target.dataset.src;}});});functioninitObserver(){constlistItems=document.querySelectorAll('.list-item-img');listItems.forEach(function(item){//监听每个列表元素observer.observe(item);});}initObserver();运行代码,观察控制台的Network,你会发现图片跟着可见区域Loading移动,我们的目的就达到了。这里提供一个在线demo供大家调试学习(ps:这里额外介绍vue-view-lazy,一个vue图片懒加载组件,也是基于IntersectionObserver实现的)。依旧是Chrome的黑科技——加载属性。从新版Chrome(76)开始,默认支持了一个新的html属性-loading,包含三个值:auto,lazy,eager(ps:之前有一篇文章是lazyload属性,后来chrome工程师已经确定为loading属性,因为lazyload的语义还不够清晰),让我们看看这三个属性的区别:auto:让浏览器自动决定是否执行懒加载,其机制尚不确定。lazy:明确让浏览器延迟加载这张图片,即只有当用户滚动到图片附近时才加载它,但没有指定这个“附近”有多近。eager:让浏览器立即加载图片不是本文的重点。我们可以使用chrome的开发工具来查看这个demo中的图片加载方法。我们删除了之前demo中所有的js脚本,只使用了属性loading=lazy。然后,勾选工具栏中的DisabledCache,仔细观察Network栏。细心的人应该会发现,一张图片被分成了两个请求!第一个状态码是206,第二个状态码是200,如图:这种现象与chrome的懒加载功能的实现机制有关:首先浏览器会发送一个预请求,请求地址就是这张图片的url,但是这个请求只拉取了这张图片的header数据,大概2kb。具体方法是在请求头中设置range:bytes=0-2047,浏览器从这个数据中可以分析出图片的宽高等基本尺寸,然后浏览器立即生成空白占位符,避免图片加载过程中页面不断跳转。这是非常合理的,用户不应该为了懒加载而牺牲其他方面的体验。酒吧?本次请求返回的状态码为206,表示客户端通过发送Range请求头捕获了资源的部分数据。详细的状态码解释请参考这篇文章。然后,当用户滚动到图片附近时,又发起一个请求,将图片的数据完整拉取。这就是我们熟悉的状态码200请求。可以预见,如果这个属性以后被广泛使用,一个服务器要处理的图片请求连接数可能会翻倍,服务器的压力会增加,但是时代在进步,我们可以依靠http2的多通道Reusable特性就是用来缓解这种压力的。这个时候就需要技术负责人权衡利弊了。需要注意的是,使用该特性进行图片延迟加载时,记得先进行兼容性处理。对于不支持该属性的浏览器,使用JavaScript代替,比如上面提到的IntersectionObserver:if("loading"inHTMLImageElement.prototype){//没问题}else{//.....}也可以锦上添花蛋糕!上面介绍的两种方法,其实最后的效果是差不多的,但是这里还有一个问题,当网速慢的时候,图片加载之前,用户会看到一段空白的时间,在这段空白的时间里,甚至渐进式图片无法发挥作用。我们需要一种更友好的显示方式来填补这个空白。有一个简单粗暴的方法,就是用占位图片代替。这个占位图片换成了Afterloadingonce,不需要重新加载就可以从缓存中取出,但是这种图片看起来会有点千篇一律,不能很好的达到预览的效果。这里我想介绍另一种占位图片的方法——css渐变色背景。原理很简单。当img标签的图片还没有加载时,我们可以为其设置背景颜色,例如:这样会先显示红色背景,然后渲染真实的画面。重要的一点是,我们需要借助工具为这张图片“准备”一个合适的渐变背景色。要实现局部预览效果,我们可以使用本文推荐的工具GIPhttps://calendar.perfplanet.com/2018/gradient-image-placeholders/进行转换,这里是在线转换的地址https://tools.w3clubs.com/gip/转换后得到如下一串代码:background:linear-gradient(tobottom,#1896f50%,#2e6d14100%)最终效果如下:4.响应式图片的做法我们经常遇到这种情况:一张在普通笔记本电脑上清晰的图片在苹果的Retina屏幕或其他高清屏幕上变得模糊。这是因为在同样大小的屏幕上,高清屏幕比普通屏幕,比如Retina屏幕,可以显示更多的物理像素。在同样的屏幕尺寸下,它的物理像素数是普通屏幕的4倍(2*2),所以在普通屏幕上显示的清晰画面在高清屏幕上会显得放大了,自然会变得模糊。屏幕上相应的显示出一个双倍大小的图像。一般来说,对于背景图片,我们可以通过css的@media进行媒体查询来决定在不同像素密度下使用哪张双图,例如:.bg{background-image:url("bg.png");width:100px;height:100px;background-size:100%100%;}@media(-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){.bg{background-image:url("bg@2x.png")//尺寸为200*200的图片}}这样做有两个好处。二是防止在低像素密度的设备上加载大尺寸图片造成的浪费。那么如何处理img标签呢?我们可以使用HTML5中img标签的srcset来实现这个效果,看下面的代码:这段代码的作用是:当设备像素密度,即dpr(devicePixelRatio)为1时,使用bg.png,为2时,使用双像bg@2x.png,以此类推,你可以根据自己的需要设置多精度加载的图片,如果没有命中,浏览器会选择距离最近的精度对应的图片加载。注意:旧版浏览器不支持srcset特性,它会继续正常加载src属性引用的图片。5.安全使用WebP图片WebP的优势这里不再赘述,简单来说:同样大小的图片,WebP可以保证比未压缩的png、jpg、gif等图片缩小40-70%。其他格式(甚至90)的比例,并保证高质量,可以支持动态图形和透明通道的显示。但是目前WebP的兼容性不是很好:但是我们可以通过两种方式来兼容还不支持webp的浏览器:图片结合source标签HTML5图片标签,可以理解为相框,可以支持多种格式,并保留默认底图:有了这段代码,浏览器就会根据是否支持webp格式自动选择加载哪张图片。如果不支持,会显示bg.jpg。如果你浏览的浏览器连图片都不支持的话,会回退到默认的img图片,这是必不可少的选项。并且这里要注意源码的放置顺序。如果把jpg放在第一位,webp放在第二位,即使浏览器支持webp,也会选择加载jpg图片。借助CDN服务自动判断目前部分图片CDN服务可以开启自动兼容webp的模式,即支持webp的浏览器会将原图转为webp图片返回,否则直接返回到原始图像。实现该功能的原理是根据浏览器发起的请求头中的Accept属性是否包含webp格式来判断:如果有则说明浏览器支持webp格式,这可能是最简单的兼容解决方案开发人员,但取决于后端服务。接下来说一下我认为应该体现的负优化方法:7、对Base64Url的反思首先回顾一下Base64的概念。Base64是一种基于64个可打印字符表示二进制数据的方法。编码过程就是从二进制数据到字符串的过程,也就是我们在web应用中经常用它来做的事情——传输图像数据。在HTML中,img的src和css样式的background-image都可以接受base64字符串,从而在页面上渲染出相应的图片。正是基于浏览器的这种能力,很多开发者提出了一种“优化方法”,将多张图片转成base64字符串,放到css样式文件中。这只有一个目的——减少HTTP请求的数量。但实际上,在如今的应用开发中,这种做法多是一种“负优化”的效果。接下来,我们来细数一下base64Url的“罪过”:第一,让css文件的大小失控,当你把图片转为base64字符串后,字符串的大小一般会比原来的大图像,通常大将近30%。如果你在一个页面上有20张平均大小为50kb的图片,将它们转换为base64,你的css文件可能会增加1.2mb的大小,这将严重阻碍浏览器的关键渲染路径:css文件本身就是一个渲染阻塞资源,如果浏览器在第一次加载时没有下载并解析所有的css内容,就无法进行渲染树的构建,而base64的嵌入会使情况变得更糟,从而异步加载原浏览器可以优化的图片,成为首屏渲染的阻塞和延迟。有人可能会说webpack的url-loader可以根据图片的大小(一般小于10kb的图片)来决定是否转base64,但是你也要担心如果页面中有100张小于10kb的图片,它将添加到css文件。体积有多大。第二,让浏览器的资源缓存策略功亏一篑。假设您的base64Url将被您的应用程序多次重用。本来浏览器可以直接从本地缓存中取出图片,换成base64Url,会造成应用中多个页面的重复下载。文字大小的1.3倍,假设一张图片大小为100kb,被你的应用使用了10次,那么流量浪费为:(1001.310)-100=1200kb。第三,低版本浏览器的兼容性是一个比较小的问题。dataurl在低版本IE浏览器中会有兼容性问题,如IE8及以下浏览器。详情请参考这篇文章。第四,不利于开发者工具调试查看。不管哪张图看起来都是一堆无意义的字符串,光看代码是无法知道哪一张是原图的,在某些情况下不利于对比。说了这么多,可能有些人不服气。既然这个解决方案有这么多的缺点,为什么在过去被广泛使用呢?这要从早期http协议的特点说起。在http1.1之前,http协议还没有实现keep-alive,即每次请求都要经过三次握手四次挥手才能建立连接。连接完成后,将被丢弃,不能重复使用,而且即使在http1.1时代,keep-alive也可以保证tcp的长期连接,不需要多次重新建立,但是因为http1.1是基于文本分割的协议,消息是串行的,必须是有序的,所以可以在这种请求“昂贵”的前提下,减少对图片资源的请求次数,而且早期图片的尺寸不是特别大,用户的响应对网页的速度和体验要求不是很高。理解。不过在越来越多的网站支持http2.0的前提下,这些都不是问题。H2是一种基于二进制帧的协议。在保留http1.1长连接的前提下,实现了消息、请求和响应的并行处理,可以交错甚至复用。大大降低了多个并行请求的开销。我不知道有什么理由继续使用base64Url。综上所述,图片优化方法总是随着浏览器功能、网络传输协议、用户体验要求的升级而更新迭代。几年前适用或有意义的优化方法,几年后将不再使用。一定还是这样。因地制宜,多管齐下,才能优化到极致!