图片通常是移动端网页中流量最大的资源。在很多类型的网站(如电子商务、社区)中,图片占据了网页的大部分空间。优化图片质量和加载速度已经成为提升用户体验的重要途径。传统方案的劣势目前有一些广泛使用的方案,比如选择压缩比更高的图片格式,使用Sprite图片,HTTP2,CDN等。5年前的知乎回答,图片优化技术有哪些前端开发?-何士君的回答-知乎仍然有效,这些解决方案在各大网站仍然发挥着重要作用。随着移动终端的兴起,各种移动设备带来了多种多样的模型尺寸和像素密度,逐渐暴露出上述方案的共性问题,即都是基于网站粒度的通用方案。我们无法保证在每个模型上的效果都是最优的,只能通过一些权衡取舍来达到更平衡的方案,以满足大多数用户的体验需求。(虽然可以通过MediaQuery等在一定程度上定制不同的屏幕,但是粒度较粗,功能有限,编写也比较复杂。)传统方案带来的弊端主要有:为了兼容旧版浏览器,使用新版浏览器用户无法享受新技术带来的更好体验。(比如不能使用压缩率更高的WebP格式)通常的2x图像方案对于小屏幕来说是一种浪费,在三屏上效果也不够好。对用户体验的追求永无止境。为了解决第一个问题,一些CDN厂商开始提供自适应WebP图片转换技术,为支持WebP的浏览器提供WebP格式的图片(例如花瓣网使用的优拍云CDN)。ServiceWorker作为PWA的核心技术,给我们带来了一些新的思路。ServiceWorkerServiceWorker是在指定源和路径下注册的事件驱动的Worker。它使用JavaScript来控制关联的页面或网站,拦截和修改访问和资源请求,并以细粒度的方式缓存资源。您可以完全控制您的应用程序在特定情况下的行为方式(最常见的是网络不可用)。MDN上的下图显示了ServiceWorker的支持。在移动端,Android有较好的支持,iOSSafari从11.3版本开始支持。整体支持率为84%(受地域、目标用户群等因素影响,经统计我司产品设备支持率为63%)。ServiceWorker的具体用法这里不再赘述。接下来重点介绍如何使用ServiceWorker对图片请求进行更细粒度的控制,达到渐进式优化的目的。使用WebPWebP是一种新的图像格式,可为网络中的图像提供更好的无损和有损压缩。使用WebP可以创建更小、更丰富的图像。WebP无损图像比PNG小26%,有损图像比具有相同SSIM索引的JPEG图像小25-34%。下图是Google的WebP兼容性列表。WebP作为Google提出的一项技术,在Android端有很好的支持,但目前的兼容性还不能广泛应用于移动端网页。默认情况下,我们还是需要根据实际情况选择合适的图片格式,在PNG/GIF/JPEG之间进行选择(比如没有透明度,色彩丰富的图片,通常使用压缩率比较高的JPEG格式)。在使用ServiceWorker的网站中,由于我们可以拦截和修改网络请求,对于支持WebP格式的浏览器,我们可以修改为请求对应的WebP图片链接;而在不受支持的浏览器中仍会请求原始链接。从而实现向后兼容优化。//sw.jself.addEventListener('fetch',(event)=>{constreq=event.request.clone()letnewUrl=req.urlif(!isImgRequest(newUrl)){return}//如果浏览器如果支持webp格式,则请求webp格式的图片constacceptHeader=req.headers.get('accept')constsupportWebp=acceptHeader&&acceptHeader.includes('webp')if(supportWebp){newUrl=getWebpUrl(newUrl)}//请求处理后的图片链接event.respondWith(fetch(newUrl,{mode:'no-cors'}))})functionisImgRequest(url){//根据url判断当前图片请求是否需要优化}functiongetWebpUrl(url){//根据图片url获取对应的webp格式url(一般图片服务器会提供图片格式转换参数)}关于DPI适配的思考对于使用rem方案实现自适应布局的网站,图片的显示宽高也会根据实际情况进行缩放。但是由于图片通常使用固定的实际尺寸(比如使用2x图片),当图片缩放时,在小屏幕上会造成浪费,但在大屏幕上效果会打折扣。如果我们能够根据不同机型获取匹配实际物理分辨率的图片进行显示,就可以最大程度的优化每台设备的用户体验。假设网站使用的图片存储服务提供图片缩放接口(例如图片https://domain/key可以通过添加参数https://domain/key将宽/高像素缩小到0.6倍/缩略图/!60p)。对于支持ServiceWorker的浏览器,可以根据设备的分辨率修改图片请求的缩放参数,从而实现设备粒度的图片尺寸定制。这里需要注意的是,ServiceWorker作为一个特殊的Worker,不能直接操作DOM,全局作用域(通过self关键字访问)只有一些与window对象相同的属性和方法。因此,我们需要通过ServiceWorker与网页的通信来获取当前设备的屏幕尺寸、DPI等信息。代码示例如下://网页代码navigator.serviceWorker.controller.postMessage({deviceWidth:window.screen.width*window.devicePixelRatio})//sw.jsconstTRIPLE_PIXEL=1242//3x水平像素让deviceWidth=0self.addEventListener('fetch',(event)=>{constreq=event.request.clone()letnewUrl=req.urlif(!isImgRequest(newUrl)){return}//如果有屏幕分辨率信息,是请求url添加缩放参数if(deviceWidth){constratio=Math.round(deviceWidth/TRIPLE_PIXEL*100)if(ratio>0&&ratio<100){newUrl=getThumbnailUrl(newUrl,ratio)}}//请求处理后的图片链接event.respondWith(fetch(newUrl,{mode:'no-cors'}))})self.addEventListener('message',({data})=>{deviceWidth=data.deviceWidth})functiongetThumbnailUrl(url,ratio){//返回添加了缩放参数的图片url}除了使用缩放参数,我们还可以导出1x/2x/3x图片(默认使用2x图片),替换根据DPI信息请求相应的
