高DPI屏幕适配:现代Web开发中的图像优化策略
发布日期: 2012年8月22日
最后更新: 2025年4月14日
注: 自本文档2012年首次发布以来,image-set和srcset已成为行业标准。本文档仍在持续更新中,但以下概述仍具有参考价值。
如今市场上存在各式各样的设备,屏幕像素密度覆盖范围极广。应用程序开发者需要支持各种像素密度,这往往颇具挑战性。在移动Web领域,以下因素使得问题更加复杂:
- 拥有各种形态因素的多样化设备
- 网络带宽和电池续航时间的限制
对于图像处理,Web开发者的目标是在保证最高图像质量的同时,尽可能高效地进行内容分发。本文将探讨当前和未来可能用到的一些关键技术。
尽可能避免使用图像
在决定必须包含图像之前,请记住Web拥有许多不依赖分辨率或DPI的强大技术。具体来说,文本、SVG和大多数CSS都能通过devicePixelRatio的自动像素缩放功能“正常工作”。
然而,在某些情况下确实无法避免使用栅格图像:
- 可能收到难以用纯SVG或CSS复制的资源
- 可能需要处理照片类内容
- 虽然可以将图像转换为SVG,但对照片进行矢量化通常没有意义,放大后的版本往往效果不佳
像素密度的发展历程
早期的计算机显示器像素密度为72或96 DPI。随着移动设备的进步,显示密度逐渐提高——用户通常将智能手机靠近面部,使得像素更加清晰可见。
到2008年,150 DPI的智能手机成为新标准。显示密度继续攀升,现在的主流智能手机已配备300 DPI的显示屏。
实际上,低密度图像在新旧屏幕上看起来是一样的,但与用户已经习惯的高密度图像的清晰度相比,低密度图像会显得不自然且粗糙。下图模拟了2x显示屏上显示1x图像的粗略效果,而2倍图像则表现优异:
1x像素 2x像素
不同像素密度的狒狒图像对比
Web上的像素处理
Web设计之初,99%的显示器都是96 DPI,几乎没有考虑过设备差异。如今面对尺寸和密度各不相同的屏幕,我们需要一种标准化方法在所有设备上正确显示图像。
HTML规范通过定义参考像素来解决这个问题,制造商使用参考像素来确定CSS像素的尺寸。
注: 参考像素是96 DPI设备上单个像素的视角,建议观看距离为臂长。对于28英寸的名义臂长,视角约为0.0213度。制造商使用参考像素来确定设备物理像素相对于标准像素或理想像素的大小,这个比率称为设备像素比。
计算设备像素比
假设智能手机屏幕的物理像素尺寸为180 PPI,设备像素比的计算分为三个步骤:
比较设备实际观看距离与参考像素距离
- 规范建议28英寸距离下96 PPI为理想值
- 智能手机通常更靠近面部,估计距离为18英寸
将距离比乘以标准密度(96 PPI),得到指定距离下的最佳像素密度:
idealPixelDensity = (28/18) * 96 ≈ 150 PPI计算物理像素密度与理想像素密度的比率,得到设备像素比:
devicePixelRatio = 180/150 = 1.2
因此,当浏览器需要根据理想或标准分辨率调整图像大小时,会参考设备像素比1.2。这意味着在此设备上,每个理想像素对应1.2个物理像素。理想像素(Web规范定义)与物理像素(设备屏幕上的点)的转换公式为:
physicalPixels = window.devicePixelRatio * idealPixels
历史上,设备供应商倾向于对devicePixelRatios(DPR)进行取整。Apple的iPhone和iPad的DPR为1,Retina级别设备为2。CSS规范建议:
像素单位应参考最接近设备像素的整数值
比率取整可以减少子像素伪影,通常效果更好。然而,现实设备环境更加多样化:Android智能手机的DPR通常为1.5,Nexus 7平板电脑的DPR约为1.33(通过类似计算得出)。预计未来会有更多可变DPR的设备,因此不应假设客户端DPR始终为整数。
HiDPI图像处理技术
解决“尽快显示最高质量图像”问题的方法很多,主要分为两大类:
- 单图像优化:使用一个图像但进行巧妙处理
- 多图像优化:使用多个图像但智能选择加载内容
单图像方法
这些方法的缺点是即使是低DPI的老设备也需要下载HiDPI图像,可能牺牲性能。
高压缩率的HiDPI图像
图像占平均网站下载带宽的60%。向所有客户端提供HiDPI图像会增加这个比例,会增加多少呢?
通过一些测试,我们设置了JPEG质量为90、50和20,生成了1x和2x图像片段。从这个小规模非科学样本来看,压缩大图像似乎能在质量和尺寸间取得良好平衡。从视觉上看,高压缩率的2x图像比未压缩的1x图像效果更好。
然而,向2x设备提供低质量、高压缩率的2x图像,结果可能不如提供高质量图像。这种方法会导致图像质量损失——比较质量90和质量20的图像,可以看到清晰度下降和颗粒感增加。对于需要高质量图像的场景(如照片查看器应用)或不容妥协的应用开发者来说,包含质量20的图像可能不合适。
注意: 此比较仅使用压缩JPEG。广泛使用的图像格式(JPEG、PNG、GIF)都有许多权衡。
优秀的图像格式:WebP
WebP是一种非常有吸引力的图像格式,能在保持图像保真度的同时实现出色的压缩效果。
检测WebP支持的一种方法是使用JavaScript:加载1像素图像的数据URI,等待加载或错误事件,然后确认尺寸正确。Modernizr附带了这样的功能检测脚本,可通过Modernizr.webp访问。
不过,更合适的方法是在CSS中直接使用image()函数。例如,如果有WebP图像和JPEG回退,可以这样写:
#pic {
background: image("foo.webp", "foo.jpg");
}
这种方法存在几个问题:首先,image()并未广泛实现;其次,尽管WebP压缩明显优于JPEG,但根据这个WebP图库的数据,尺寸仅减少约30%,改善相对有限。因此,仅靠WebP不足以解决高DPI问题。
渐进式图像格式
渐进式图像格式(如JPEG 2000、渐进式JPEG、渐进式PNG、GIF等)的优点是图像在完全加载前就能显示(这点尚有争议)。虽然可能产生尺寸开销,但证据存在矛盾:Jeff Atwood声称渐进式模式“PNG图像尺寸增加约20%,JPEG和GIF图像增加约10%”,而Stoyan Stefanov则认为对于大文件,渐进式模式通常更高效。
乍看之下,渐进式图像在“尽快提供最高质量图像”方面前景广阔。如果发现额外数据不能提高图像质量(所有保真度改进都在子像素级别),浏览器可以停止下载和解码图像。
连接可以立即关闭,但重新建立通常需要成本。对于图像丰富的网站,保持一个HTTP连接存活并尽可能长时间地重用是最有效的方法。如果一个图像因下载足够而提前关闭连接,浏览器需要创建新连接,这在低延迟环境中可能非常缓慢。
解决此问题的一种方法是使用HTTP Range请求,让浏览器指定要获取的字节范围。智能浏览器可以发送HEAD请求获取头部信息,处理并决定实际需要的图像数量,然后再获取。遗憾的是,HTTP Range在Web服务器上支持不足,使得这种方法不实用。
最后,这种方法的明显限制是无法选择加载哪个图像,只能改变同一图像的保真度,因此无法满足“艺术指导”的使用场景。
多图像方法
这些方法需要开发者创建同一资源的多个版本,并制定决策策略。可用的选择方法包括:
- JavaScript
- 服务器端传递
- CSS媒体查询
- 浏览器内置功能(
image-set())
使用JavaScript决定加载的图像
第一种确定加载图像的方法是使用客户端JavaScript。这种方法可以了解用户代理的所有信息并相应处理:使用window.devicePixelRatio确定设备像素比,获取屏幕宽度和高度,甚至可以通过navigator.connection嗅探网络连接,或像foresight.js库那样发出虚假请求。收集所有这些信息后,就可以决定加载哪个图像。
存在数以百万计的使用此技术的JavaScript库,遗憾的是,没有哪个特别出色。一个主要缺点是图像加载会延迟到预解析器完成工作,意味着在页面加载事件触发前不会开始图像下载。详情请参阅Jason Grigsby的文章。
服务器端决定加载的图像
要在服务器端延迟决策,可以为每个提供的图像创建自定义请求处理程序。此类处理程序基于User-Agent(与服务器共享的唯一信息)检查Retina支持,然后根据服务器端逻辑决定是否提供HiDPI资源,加载遵循已知命名约定的适当资源。
不幸的是,User-Agent并不总是包含足够信息来判断设备应接收高质量还是低质量图像。请避免使用User-Agent决定样式的解决方案。
使用CSS媒体查询
CSS媒体查询是声明式的,可以明确表达意图,让浏览器负责适当处理。除了最常见的设备尺寸匹配用途外,还可以匹配devicePixelRatio。相关媒体查询是device-pixel-ratio,不出所料,也有相关的最小和最大变体。
要在设备像素比超过阈值时加载高DPI图像,可以使用以下方法:
#my-image { background: url(low.png); }
@media only screen and (min-device-pixel-ratio: 1.5) {
#my-image { background: url(high.png); }
}
各种供应商前缀的混合会使情况稍微复杂,特别是“min”和“max”前缀的放置差异很大:
@media only screen and (min--moz-device-pixel-ratio: 1.5),
(-o-min-device-pixel-ratio: 3/2),
(-webkit-min-device-pixel-ratio: 1.5),
(min-device-pixel-ratio: 1.5) {
#my-image {
background: url(high.png);
}
}
这种方法重新获得了JavaScript解决方案中失去的预解析优势。还获得了选择响应式断点的灵活性(如低DPI、中DPI、高DPI图像),这在服务器端方法中是不可能的。
遗憾的是,这种方法仍然不够优雅,可能导致CSS看起来奇怪或需要预处理。此外,它限于CSS属性,无法设置<img>标签,所有图像都必须是带背景的元素。最后,严格依赖设备像素比可能导致高DPI智能手机在EDGE连接状态下下载巨大的2x图像资源,这不是最佳用户体验。
image-set()是CSS函数,因此不解决<img>标签的问题。输入@srcset可以解决这个问题。
支持高DPI的浏览器功能
最终,选择哪种高DPI支持方法取决于具体需求,上述所有方法都有缺点。
由于image-set和srcset得到广泛支持,它们是最佳解决方案。还有其他最佳实践可以增强与旧浏览器的兼容性。
这两个功能的区别是什么?image-set()是CSS函数,可用作CSS属性background的值。srcset是<img>元素特有的属性,语法也类似。虽然两者都可以指定图像声明,但使用srcset属性还可以根据视口大小配置要加载的图像。
图像集最佳实践
image-set()语法接受一个或多个逗号分隔的图像声明。每个声明包含URL字符串或url()函数,后跟适当的分辨率说明符。例如:
image-set(
url("image1.jpg") 1x,
url("image2.jpg") 2x
);
/* 也可以不使用`url()`包含image-set */
image-set(
"image1.jpg" 1x,
"image2.jpg" 2x
);
这告诉浏览器有两个图像可供选择:一个针对1x显示器优化,另一个针对2x显示器优化。浏览器根据各种因素(如果浏览器足够智能,还包括网络速度)选择加载哪一个。
浏览器不仅会加载正确的图像,还会适当缩放图像。这意味着浏览器假设2x图像是1x图像的两倍大,并将2x图像缩小两倍,使其在页面上显示为相同尺寸。
除了指定1x、1.5x、Nx外,还可以用DPI指定特定设备像素密度。
如果担心旧浏览器不支持image-set属性,可以添加回退图像确保显示:
/* 回退支持 */
background-image: url(icon1x.jpg);
background-image: image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
此示例代码在支持image-set的浏览器中加载适当的资源,否则回退到1x资源。
为什么不直接为image-set()创建polyfill(JavaScript垫片)并结束呢?实际上,为CSS函数实现高效polyfill非常困难。(详细原因请参阅此www-style讨论)。
图像srcset属性
srcset元素除了提供image-set的声明外,还接受宽度和高度值来匹配视口尺寸,尝试提供最相关的版本。
<img src="banner.jpeg"
srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">
此示例中,视口宽度小于640像素的设备接收banner-phone.jpeg,小屏幕高DPI设备接收banner-phone-HD.jpeg,屏幕超过640像素的高DPI设备接收banner-HD.jpeg,其他设备接收banner.jpeg。
在图像元素中使用image-set
虽然可以用带背景的<div>替换img元素来使用image-set方法,但这需要谨慎考虑。缺点在于<img>标签具有重要的语义值,对可访问性和Web爬虫都很重要。
使用content CSS属性可以根据devicePixelRatio自动缩放图像。例如:
/* 使用content属性 */
img.high-dpi {
content: image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
}
填充srcset
srcset的一个便利功能是它带有自然回退:如果未实现srcset属性,所有浏览器都会处理src属性。此外,由于它只是HTML属性,也可以用JavaScript创建polyfill。
此polyfill附带单元测试,尽可能接近规范。还包括检查,确保如果srcset已原生实现,polyfill不会执行代码。
总结
高DPI图像的最佳解决方案是使用SVG和CSS。但对于图像密集型网站,这可能并不现实。
JavaScript、CSS和服务器端解决方案的方法各有优缺点。最有前景的方法是使用image-set和srcset。
总之,建议如下:
- 对背景图像使用image-set,并为不支持浏览器提供适当替代方案
- 对于内容图像,使用srcset polyfill,或回退到使用image-set(如上所述)
- 如果可以牺牲图像质量,考虑使用高压缩率的2x图像
正在加载评论...