可变像素密度的高 DPI 图像
当今复杂设备环境的一个特点是有 非常广泛的屏幕像素密度 可供选择。 一些设备具有非常高分辨率的显示屏,而其他设备则落后。 应用程序开发人员需要支持一系列像素密度,这可能非常具有挑战性。 在移动网络上,挑战因以下几个因素而变得更加复杂:
- 具有不同外形规格的各种设备。
- 网络带宽和电池寿命受限。
在图像方面,Web 应用程序开发人员的目标是 尽可能高效地提供质量最好的图像 。 本文将介绍一些在今天和不久的将来执行此操作的有用技术。
尽可能避免图片
在打开这罐蠕虫之前,请记住网络有许多强大的技术,这些技术在很大程度上与分辨率和 DPI 无关。 具体来说,由于网络的自动像素缩放功能(通过 devicePixelRatio ),文本、SVG 和大部分 CSS 将“正常工作”。
也就是说,您不能总是避免光栅图像。 例如,您可能获得了很难在纯 SVG/CSS 中复制的资产,或者您正在处理一张照片。 虽然您可以自动将图像转换为 SVG,但矢量化照片意义不大,因为放大版本通常看起来不太好。
背景
显示密度的非常短暂的历史
早期,计算机显示器的像素密度为 72 或 96dpi( 每英寸点数 )。
显示器的像素密度逐渐提高,这在很大程度上是由移动用例推动的,在这种情况下,用户通常将手机靠近自己的脸,从而使像素更加可见。 到 2008 年,150dpi 手机成为新标准。 显示密度增加的趋势仍在继续,今天的新手机采用 300dpi 显示屏(Apple 品牌为“Retina”)。
当然,圣杯是像素完全不可见的显示器。 对于手机外形,当前一代的 Retina/HiDPI 显示屏可能接近理想状态。 这样的新型硬件和可穿戴设备 但是像Project Glass 可能会继续推动像素密度的增加。
实际上,低密度图像在新屏幕上看起来应该与在旧屏幕上看起来一样,但与高密度用户习惯看到的清晰图像相比,低密度图像看起来不和谐且像素化。 以下是 1x 图像在 2x 显示器上的外观粗略模拟。 相比之下,2x 图像看起来相当不错。
狒狒! 在不同的像素密度。
网络上的像素
在设计 Web 时,99% 的显示器都是 96dpi(或 假装是 ),并且很少为这方面的变化做出规定。 由于屏幕尺寸和密度差异很大,我们需要一种标准方法来使图像在各种屏幕密度和尺寸下看起来都不错。
HTML 规范 最近通过定义制造商用来确定 CSS 像素大小的参考像素来解决这个问题。
建议参考像素为像素密度为96dpi且距离阅读器一臂远的设备上一个像素的视角。 因此,对于 28 英寸的标称臂长,视角约为 0.0213 度。
使用参考像素,制造商可以确定设备物理像素相对于标准或理想像素的大小。 该比例称为设备像素比。
计算设备像素比
假设智能手机的屏幕物理像素大小为每英寸 180 像素 (ppi)。 计算设备像素比需要三个步骤:
将设备握持的实际距离与参考像素的距离进行比较。
根据规格,我们知道在 28 英寸时,理想的像素是每英寸 96 个像素。 然而,由于它是一部智能手机,与手持笔记本电脑相比,人们将手机拿得离脸更近。 让我们估计该距离为 18 英寸。
将距离比乘以标准密度 (96ppi) 以获得给定距离的理想像素密度。
idealPixelDensity = (28/18) * 96 = 每英寸 150 像素(大约)
取物理像素密度与理想像素密度之比,得到设备像素比。
devicePixelRatio
= 180/150 = 1.2
显示一个参考角度像素的图表,以帮助说明如何计算 devicePixelRatio。
因此,现在当浏览器需要知道如何根据理想或标准分辨率调整图像大小以适合屏幕时,浏览器会参考设备像素比 1.2 - 也就是说,对于每个理想像素,该设备有 1.2 个物理像素. 介于理想(由网络规范定义)和物理(设备屏幕上的点)像素之间的公式如下:
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 图像
- 非常棒的图像格式
- 渐进式图像格式
多图像方法:使用多图像,但要巧妙地选择要加载的图像。 这些方法具有固有的开销,使开发人员可以创建同一资产的多个版本,然后找出决策策略。 以下是选项:
- JavaScript
- 服务器端交付
- CSS 媒体查询
- 内置浏览器功能(
image-set()
,<img srcset>
)
高度压缩的 HiDPI 图像
图片已经 带宽的 60% 占据了下载普通网站所花费的 。 通过向所有客户端提供 HiDPI 图像,我们将增加这个数字。 它会增长多少?
我运行了一些测试,生成了 JPEG 质量为 90、50 和 20 的 1x 和 2x 图像片段。这是 shell 脚本 (使用 ImageMagick ): 我用来生成它们的
不同压缩和像素密度的图像样本。
从这个小的、不科学的样本来看,压缩大图像似乎提供了一个很好的质量与大小的权衡。 在我看来,高度压缩的 2x 图像实际上比未压缩的 1x 图片看起来更好。
当然,向 2x 设备提供低质量、高度压缩的 2x 图像比提供高质量图像更糟糕,并且上述方法会导致图像质量下降。 如果您将 quality: 90 图像与 quality: 20 图像进行比较,您会发现清晰度下降,颗粒度增加。 在高质量图像是关键(例如照片查看器应用程序)或不愿妥协的应用程序开发人员的情况下,这些伪像可能是不可接受的。
上面的比较完全是用压缩的 JPEG 进行的。 值得注意的是,广泛实施的图像格式(JPEG、PNG、GIF)之间存在 许多权衡 ,这使我们...
非常棒的图像格式
WebP 是一种非常 引人注目的图像格式 ,可以很好地压缩,同时保持图像的高保真度。 当然,它还 没有在所有地方实施 !
一种方法是通过 JavaScript 检查 WebP 支持。 您通过 data-uri 加载 1px 图像,等待加载或触发错误事件,然后验证大小是否正确。 Modernizr 附带了这样一个 特征检测脚本 ,可以通过 Modernizr.webp
.
然而,更好的方法是直接在 CSS 中使用 image() 函数 。 所以如果你有一个 WebP 图像和 JPEG 回退,你可以写如下:
#pic {
background: image("foo.webp", "foo.jpg");
}
这种方法存在一些问题。 首先, image()
根本没有广泛实施。 其次,虽然 WebP 压缩将 JPEG 打得落花流水,但它仍然是一个相对渐进的改进——基于这个 WebP 画廊, 大约小了 30% 。 因此,仅靠 WebP 不足以解决高 DPI 问题。
渐进式图像格式
诸如 JPEG 2000、Progressive JPEG、Progressive PNG 和 GIF 之类的渐进图像格式具有(有些争议)在图像完全加载之前看到图像就位的好处。 它们可能会产生一些规模开销,尽管关于这一点的证据相互矛盾。 Jeff Atwood 声称 渐进模式“将 PNG 图像的大小增加了大约 20%,将 JPEG 和 GIF 图像的大小增加了大约 10%”。 然而, Stoyan Stefanov 声称 对于大文件,渐进模式更有效(在大多数情况下)。
乍一看,在尽可能快地提供最佳质量图像的情况下,渐进式图像看起来很有前途。 这个想法是,一旦浏览器知道附加数据不会提高图像质量(即所有保真度改进都是子像素),它就可以停止下载和解码图像。
虽然连接很容易终止,但重新启动它们通常代价高昂。 对于包含许多图像的站点,最有效的方法是保持单个 HTTP 连接处于活动状态,并尽可能长时间地重复使用它。 环境中可能非常 如果连接因为一个图像已经下载足够多而过早终止,则浏览器需要创建一个新连接,这在低延迟 慢。
一种解决方法是使用 HTTP Range 请求,它允许浏览器指定要获取的字节范围。 智能浏览器可以发出 HEAD 请求以获取标头,对其进行处理,确定实际需要多少图像,然后获取。 不幸的是,Web 服务器对 HTTP Range 的支持很差,这使得这种方法不切实际。
最后,这种方法的一个明显限制是您无法选择要加载的图像,只能选择同一图像的不同保真度。 因此,这并没有解决“ 艺术指导 ”用例。
使用 JavaScript 决定加载哪个图像
决定加载哪个图像的第一个也是最明显的方法是在客户端中使用 JavaScript。 这种方法可以让你找出关于你的用户代理的一切,并做正确的事情。 您可以通过以下方式确定设备像素比 window.devicePixelRatio
,获取屏幕宽度和高度,甚至可能通过 navigator.connection 进行一些网络连接嗅探或发出虚假请求,就像 foresight.js 库 所做的那样。 收集完所有这些信息后,您可以决定加载哪个图像。
大约有 100 万个 JavaScript 库 执行上述操作,但不幸的是,它们都不是特别出色。
这种方法的一大缺点是使用 JavaScript 意味着您将延迟图像加载,直到先行解析器完成。 这实质上意味着图像甚至不会开始下载,直到 pageload
事件触发。 中有更多相关内容 在Jason Grigsby 的文章 。
决定在服务器上加载什么图像
您可以通过为您服务的每个图像编写自定义请求处理程序来将决定推迟到服务器端。 这样的处理程序将根据 User-Agent(中继到服务器的唯一信息)检查 Retina 支持。 然后,根据服务器端逻辑是否要为 HiDPI 资产提供服务,加载适当的资产(根据一些已知约定命名)。
不幸的是,用户代理不一定提供足够的信息来决定设备应该接收高质量还是低质量的图像。 此外,不用说,与 User-Agent 相关的任何内容都是 hack,应尽可能避免。
使用 CSS 媒体查询
作为声明式的,CSS 媒体查询让您陈述您的意图,并让浏览器代表您做正确的事情。 除了媒体查询最常见的用途——匹配设备尺寸——你还可以匹配 devicePixelRatio
. 关联的媒体查询是设备像素比,并且如您所料,具有关联的最小和最大变体。 如果你想加载高 DPI 图像并且设备像素比超过阈值,你可以这样做:
#my-image { background: (low.png); }
@media only screen and (min-device-pixel-ratio: 1.5) {
#my-image { background: (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);
}
}
使用这种方法,您可以重新获得前瞻解析的好处,而 JS 解决方案则失去了这一点。 您还可以获得选择响应断点的灵活性(例如,您可以拥有低、中和高 DPI 图像),这是服务器端方法所没有的。
不幸的是,它仍然有点笨拙,并导致看起来很奇怪的 CSS(或需要预处理)。 此外,这种方法仅限于 CSS 属性,因此无法设置 <img src>
,并且您的图像必须全部是具有背景的元素。 最后,通过严格依赖设备像素比,您可能会遇到这样的情况:您的高 DPI 智能手机在 EDGE 连接 上最终下载了大量 2 倍的图像资产。 这不是最好的用户体验。
使用新的浏览器功能
最近有很多关于网络平台支持高 DPI 图像问题的讨论。 Apple 最近闯入了这个领域,将 image-set() CSS 函数引入了 WebKit。 因此,Safari 和 Chrome 都支持它。 因为它是一个 CSS 函数, image-set()
没有解决问题 <img>
标签。 输入 @srcset ,它解决了这个问题,但(在撰写本文时)还没有参考实现(还!)。 下一节将深入探讨 image-set
和 srcset
.
支持高 DPI 的浏览器功能
最终,关于采用哪种方法的决定取决于您的特定要求。 也就是说,请记住,所有上述方法都有缺点。 期待,然而,曾经 image-set
和 srcset 得到广泛支持,它们将是解决这个问题的合适方法。 现在,让我们谈谈一些可以让我们尽可能接近理想未来的最佳实践。
首先,这两个有什么不同? 出色地, image-set()
是一个 CSS 函数,适合用作背景 CSS 属性的值。 srcset 是特定于的属性 <img>
元素,具有相似的语法。 这两个标记都允许您指定图像声明,但 srcset 属性还允许您根据视口大小配置要加载的图像。
图像集的最佳实践
这 image-set()
CSS 函数的前缀为 -webkit-image-set()
. 语法非常简单,采用一个或多个逗号分隔的图像声明,其中包含 URL 字符串或 url()
函数后跟相关的分辨率。 例如:
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
这告诉浏览器有两个图像可供选择。 其中一个针对 1x 显示器进行了优化,另一个针对 2x 显示器进行了优化。 如果浏览器足够智能(据我所知目前尚未实现),则浏览器会根据多种因素选择加载哪个,甚至可能包括网络速度。
除了加载正确的图像外,浏览器还会相应地缩放它。 换句话说,浏览器假定 2 张图片是 1x 图片的两倍大,因此会将 2x 图片缩小 2 倍,这样图片在页面上看起来大小相同。
除了指定 1x、1.5x 或 Nx,您还可以指定特定的设备像素密度(以 dpi 为单位)。
这很好用,除了在不支持的浏览器中 image-set
属性,它根本不会显示任何图像! 这显然很糟糕,因此您 必须 使用回退(或一系列回退)来解决该问题:
background-image: url(icon1x.jpg);
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
/* This will be useful if image-set gets into the platform, unprefixed.
Also include other prefixed versions of this */
background-image: image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
以上将在支持图像集的浏览器中加载适当的资产,否则回退到 1x 资产。 明显的警告是,虽然 image-set()
浏览器支持很低,大多数用户代理将获得 1x 资产。
这个演示 使用 image-set()
加载正确的图像,如果不支持此 CSS 功能,则回退到 1x 资源。
此时,您可能想知道为什么不只是 polyfill(即构建一个 JavaScript 填充程序) image-set()
收工了吗? 事实证明,为 CSS 函数实现高效的 polyfill 是相当困难的。 (有关原因的详细解释,请参阅此 www-style discussion )。
图像资源集
这是 srcset 的示例:
<img alt="my awesome image"
src="banner.jpeg"
srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">
如您所见,除了 x 声明 image-set
提供,srcset 元素还采用与视口大小相对应的 w 和 h 值,试图提供最相关的版本。 上面的 banner-phone.jpeg 用于视口宽度低于 640px 的设备,banner-phone-HD.jpeg 用于小屏幕高 DPI 设备,banner-HD.jpeg 用于屏幕大于 640px 的高 DPI 设备,banner.jpeg其他一切。
对图像元素使用图像集
因为 img 元素上的 srcset 属性在大多数浏览器中都没有实现,所以可能很想将 img 元素替换为 <div>
带有背景并使用图像设置方法。 这将起作用,但需要注意。 这里的缺点是 <img>
标签具有长期的语义价值。 实际上,这主要是出于网络爬虫和可访问性的原因。
如果你最终使用 -webkit-image-set
,您可能会想使用 background CSS 属性。 这种方法的缺点是您需要指定图像大小,如果您使用的是非 1x 图像,这是未知的。 除了这样做,您还可以使用 content CSS 属性,如下所示:
<div
style="content: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x);">
</div>
这将根据 devicePixelRatio 自动缩放图像。 请参阅 上述技术的实际应用示例 ,并附加回退到 url()
对于不支持的浏览器 image-set
.
Polyfilling srcset
一个方便的功能 srcset
是它带有自然的回退。 在没有实现 srcset 属性的情况下,所有浏览器都知道要处理 src 属性。 此外,由于它只是一个 HTML 属性,因此可以 使用 JavaScript 创建 polyfill 。
这个 polyfill 带有 单元测试 接近规范 。 ,以确保它尽可能 此外,如果 srcset 是本机实现的,还有一些检查可以防止 polyfill 执行任何代码。
这是 polyfill 的 实际演示。
结论
没有解决高 DPI 图像问题的灵丹妙药。
最简单的解决方案是完全避免图像,而是选择 SVG 和 CSS。 然而,这并不总是现实的,尤其是当您的站点上有高质量图像时。
JS、CSS 和使用服务器端的方法各有优缺点。 然而,最有前途的方法是利用新的浏览器功能。 虽然浏览器支持 image-set
和 srcset
仍然不完整,今天可以使用合理的回退。
总而言之,我的建议如下:
- 对于背景图像,请使用 图像集 ,并为不支持它的浏览器提供适当的回退。
- 对于内容图像,使用 srcset polyfill ,或回退到 使用图像集 (见上文)。
- 对于您愿意牺牲图像质量的情况,请考虑使用高度 压缩的 2x 图像 。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 创建高 DPI Canvas 画布
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论