高dpi图片对于不同设备的适配方案

LorenzoBarn 7年前
   <p>在当今日益复杂的设备领域,屏幕的可用像素密度已经变得非常广泛。</p>    <p>既有非常高分辨率的显示设备,也有远远落后的设备。</p>    <p>应用程序开发人员需要支持一系列像素密度的显示设备,这可能是相当具有挑战性的。</p>    <p>在移动web端,情况变得更加复杂:</p>    <ul>     <li>各种各样的设备具有不同的外形尺寸。</li>     <li>受限的网络带宽和电池寿命。<br> 在图片方面,Web应用程序开发人员的目标是尽可能高效地提供最佳质量的图像。<br> 本文将介绍适用于现在和不久将来的有效技术来达到这一效果。</li>    </ul>    <h2>如果可能,尽量避免使用图片</h2>    <p>打开这个蠕虫之前,请记住,Web有许多强大的技术,主要是分辨率和DPI独立。 具体来说,由于web的自动像素缩放功能(通过devicePixelRatio),文本,SVG和大多数CSS将“只工作”。</p>    <p>也就是说,你不能总是回避使用图片。</p>    <p>例如,当你在处理一些图片资源的时候,很难用纯svg或css来处理。</p>    <p>把图片自动转为svg并无太大意义,因为只是把图片简单的放大,看起来会比较模糊。</p>    <h2>高DPI图片技术概览</h2>    <p>有许多技术用于解决尽可能快地显示最佳质量图像的问题,大致分为两类:</p>    <ul>     <li>单张图片进行质量优化</li>     <li>多张图片选择性展示</li>    </ul>    <p>单图片解决方案:对一张图片进行巧妙地处理。</p>    <p>缺点就是不可避免地牺牲在某些设备上的性能,因为即使在具有较低DPI的旧设备上也将下载高DPI的图片。</p>    <p>包含以下几种实现方式:</p>    <ul>     <li>高压缩的高DPI图片</li>     <li>webp图片格式</li>     <li><a href="/misc/goto?guid=4959737597821109311" rel="nofollow,noindex">渐进式的图片格式</a></li>    </ul>    <p>多图片解决方案:使用多张图片,选择最优的图片进行显示。这种方式会额外增加开发人员的工作量,因为针对每个图片都要创建多个版本,并使用最优的选择策略。一些可选的方式:</p>    <ul>     <li>Javascript</li>     <li>服务端转发</li>     <li>css媒体查询</li>     <li>利用浏览器内置特性(image-set(), sercset)</li>    </ul>    <h2>高压缩的高DPI图片</h2>    <p>图片资源通常占网站下载带宽的60%,如果提供高DPI图片给所有客户端,这一占比将继续扩大。那么具体情况如何?</p>    <p>我用了一些测试脚本来生成图像质量分别为90%,50%,20%的1x图和2x图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/78fde7d0d92d9a300b75c7f5c45b6e1b.jpg"></p>    <p>从这个小的不太科学的样本来看,似乎压缩大图像提供了一个良好的质量尺寸权衡。</p>    <p>对于我们的眼睛,高压缩比的2x图像实际上看起来比未压缩的1x图片更好。</p>    <p>当然,向2x设备提供低质量,高压缩比的2x图片远不如提供高质量的图片,并且上述方法将导致图像质量损失。</p>    <p>如果你比较90%图像质量和20%图像质量的图片,你会感到明显的失真和颗粒感。</p>    <p>在对图片质量有较高要求的情况下(例如,照片查看器应用程序),或者对于不愿意妥协的应用程序开发人员来说,这些图片是不可接受的。</p>    <p>上述比较使用了未压缩的JPEG图片。值得注意的是,在广泛使用的图像格式(JPEG,PNG,GIF)之间还需要进行更多的折衷和取舍,这使我们选择了另一种处理方式…</p>    <h2>webp图片格式</h2>    <p>WebP是一个非常引人注目的图像格式,压缩非常好,同时保持高图像保真度。 当然它并不是所有情况下可用!</p>    <p>一种方法是通过JavaScript检查是否支持WebP。</p>    <p>通过data-uri加载1x图像,等待加载或错误事件触发,然后验证大小是否正确。 Modernizr附带了这样的功能检测脚本,可通过Modernizr.webp获得。</p>    <p>更好的实现方式是在css中使用image()函数。如果你有webp或者jpeg格式的图片,可以写成:</p>    <pre>  <code class="language-css">#pic {    background: image("foo.webp", "foo.jpg");  }  </code></pre>    <p>这种方法有一些问题。</p>    <p>首先,image()没有被广泛实现。</p>    <p>第二,虽然WebP压缩打破了JPEG的压缩极限,它仍然是一个相对性的改进 - 体积减少不到30%。</p>    <p>因此,单独的WebP不足以解决高DPI问题。</p>    <h2>渐进式图片格式</h2>    <p>渐进图像格式,如JPEG 2000,Progressive JPEG,Progressive PNG和GIF的好处就是在图片完全加载之前能看到图片。</p>    <p>这可能会产生一些额外的开销。 Jeff Atwood 声称渐进式图片“增加了约20%的PNG图像的大小,和约10%的JPEG和GIF图像的大小”。</p>    <p>然而, Stoyan Stefanov 声称,对于大文件,渐进式图片更高效(在大多数情况下)。</p>    <p>乍一看,渐进图像看起来非常有前途,能尽可能快地提供最好的质量图片。</p>    <p>现实是,浏览器一旦知道额外的数据无法提高图片质量(所有保真度的改进是基于子像素的),可能停止继续下载和解码图片。</p>    <p>虽然连接容易终止,但是重新启动它们通常是昂贵的。</p>    <p>对于具有许多图像的站点,最有效的方法是保持单个HTTP连接活动,尽可能长时间地重复使用它。</p>    <p>一张图片下载完毕,浏览器将关闭当前连接,然后再创建新的连接,这在弱网络环境中可能真的很慢。</p>    <p>对此的一种解决方法是使用HTTP Range请求,它允许浏览器指定要提取的字节范围。</p>    <p>智能浏览器可以做出HEAD请求来获取标题,处理它,决定实际需要多少图片然后获取。</p>    <p>不幸的是,HTTP Rang在Web服务器中支持不足,使得这种方法不切实际。</p>    <p>最后,这种方法的一个明显的限制是,你不能选择图像的分辨率,只能选择相同图像的不同的保真度。因此不能满足“艺术级别”的用户体验。</p>    <h2>用javascript选择图片进行加载</h2>    <p>最明显的方法是在客户端中使用JavaScript来决定加载哪一种图片。</p>    <p>这种方法需要获取浏览器的信息来进行判断。</p>    <p>可以通过 window.devicePixelRatio 获取设备像素比率,获取屏幕宽度和高度,甚至可能通过navigator.connection或发出假请求,如foresight.js库做一些网络连接嗅探。</p>    <p>收集所有这些信息后再决定要加载哪个图片。</p>    <p>有大约 一百万个JavaScript库 做上面的事情,不幸的是没有一个是特别好用的。</p>    <p>这种方法的一个大缺点是,使用JavaScript意味着将延迟图像加载,直到前瞻解析器完成。</p>    <p>这实质上意味着在pageload事件触发之前,图片甚至不会开始下载。</p>    <h2>由服务端选择图片</h2>    <p>可以通过为每个图片编写自定义请求处理程序来处理。</p>    <p>这样的处理程序将基于User-Agent(中继到服务器的唯一信息)检查Retina支持。</p>    <p>然后,根据服务器端逻辑决定是否要提供高DPI图片来加载适当的资产(根据一些已知的惯例命名)。</p>    <p>不幸的是,用户代理不一定提供足够的信息来决定设备是否应该接收高质量或低质量的图像。</p>    <p>此外,与User-Agent相关的任何内容都可能成为被攻击的漏洞,应该尽量避免使用。</p>    <h2>用css媒体查询</h2>    <p>CSS媒体查询可以让浏览器知道你的意图并加载正确的的代码。 除了最常见的媒体查询使用 - 匹配设备大小 - 还可以匹配devicePixelRatio。 相关联的媒体查询是device-pixel-ratio,并且有min和max值可以设置。 如果要加载高DPI图片,且设备像素比率超过阈值,则可以执行以下操作:</p>    <pre>  <code class="language-css">#my-image { background: (low.png); }    @media only screen and (min-device-pixel-ratio: 1.5) {    #my-image { background: (high.png); }  }  </code></pre>    <p>使用这种方法,可以重获前瞻性解析的好处,而JS解决方案失去了它。</p>    <p>还可以灵活地选择响应断点(例如,可以加载低,中和高DPI的图片),当某些图片请求出错的时候。</p>    <p>不幸的是,它仍然有点笨拙,还需要编写且看起来奇怪的css。 此外,此方法仅限于CSS属性,因此无法设置。所有的图片必须都是背景元素。</p>    <h2>使用新浏览器特性</h2>    <p>最近有很多关于web平台支持的高DPI图片问题的讨论。 苹果最近把image-set()这个CSS函数添加到了WebKit。 因此,Safari和Chrome都支持它。 由于它是一个CSS函数,image-set()没有解决标签的问题。 srcset属性解决了这个问题, 下面将更深入地讨论image-set和srcset。</p>    <h3>image-set</h3>    <p>image-set 函数使用非常简单,在webkit下需要添加前缀:</p>    <pre>  <code class="language-css">background-image:  -webkit-image-set(    url(icon1x.jpg) 1x,    url(icon2x.jpg) 2x  );  </code></pre>    <p>它将告诉浏览器有两张图片可选。一张是1x图,一张是2x图。然后浏览器会根据一系列因素来自动选择合适的图片加载。</p>    <p>另外浏览器将会子自动缩放对应的图片大小进行加载。</p>    <p>除了设置 1x,1.5x或Nx之外,还可以指定其它设备像素密度。</p>    <p>这种方式比较理想,除开在那些不支持 image-set函数的浏览器上(将不显示任何图片!这太悲剧了,所以需要备用策略)。</p>    <pre>  <code class="language-css">background-image: url(icon1x.jpg);  background-image: -webkit-image-set(    url(icon1x.jpg) 1x,    url(icon2x.jpg) 2x  );    background-image: image-set(    url(icon1x.jpg) 1x,    url(icon2x.jpg) 2x  );  </code></pre>    <p>支持image-set函数的浏览器将选择图片进行加载,那些不支持的将加载1x图片。</p>    <p>明显的缺陷就是在不支持image-set函数的浏览器上只会加载1x图片。</p>    <h3>srcset</h3>    <pre>  <code class="language-css"><img alt="my awesome image"    src="banner.jpeg"    srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">  </code></pre>    <p>如上所示,除了image-set提供的x声明之外,srcset元素还接受对应于视口大小的w和h值,试图提供最相关的版本。</p>    <p>上面将为banner-phone.jpeg提供视口宽度在640像素以下的设备,banner-phone-HD.jpeg到小屏幕高DPI设备,banner-HD.jpeg到高DPI设备,屏幕大于640px,以及banner.jpeg 到一切。</p>    <p>因为img元素的srcset属性在大多数浏览器中没有实现,所以你可能很容易想到用带有背景的</p>    <p>替换img元素,并使用image-set函数。这种方式可以正常显示,但缺点就是,标签是具有语义的,使用div降低了爬虫的可访问性。</p>    <h2>结论</h2>    <p>没有解决高DPI图片问题的银弹。</p>    <p>最简单的解决方案是完全避免图像,选择SVG和CSS。 但是,这并不现实,特别是如果网站上有高品质的图片。</p>    <p>JS,CSS和使用服务器端的方法都有自己的优点和缺点。 然而,最有希望的方法是利用新的浏览器功能。 虽然浏览器对image-set和srcset的支持仍然不完整。</p>    <p> </p>    <p> </p>    <p>来自:http://yalishizhude.github.io/2017/02/16/dpi-images/</p>    <p> </p>