前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【学习图片】11.描述性语法

【学习图片】11.描述性语法

作者头像
前端小智@大迁世界
发布2023-03-08 14:49:21
1.1K0
发布2023-03-08 14:49:21
举报
文章被收录于专栏:终身学习者终身学习者

使用srcsetsizes来向浏览器提供有关图像来源和它们如何被使用的信息。

在这个模块中,我们将学习如何为浏览器提供一系列图像选择,以便它可以做出最佳的显示决策。srcset不是在特定断点切换图像源的方法,也不是为了将一张图像换成另一张。这些语法允许浏览器独立地解决一个非常困难的问题:无缝地请求和渲染一个适合用户浏览上下文的图像源,包括视口大小、显示密度、用户偏好、带宽和一些其他因素。

这是一个巨大的要求 - 当我们只是简单地标记一个图像的时候,它肯定超出了我们的想象范围,并且做得好需要比我们能够访问的信息更多。

使用 x 描述密度

一个固定宽度的<img>在任何浏览上下文中占据的视口空间相同,无论用户显示器的密度(屏幕上的物理像素数量)如何。例如,固有宽度为400px的图像在原始的Google Pixel和较新的Pixel 6 Pro上几乎占据整个浏览器视口 - 这两个设备都有一个标准化的412px逻辑像素宽的视口。

然而,Pixel 6 Pro具有更清晰的显示:6 Pro的物理分辨率为1440×3120像素,而Pixel为1080×1920像素,即构成屏幕本身的硬件像素数量。

设备的逻辑像素和物理像素之间的比率是该显示的设备像素比(DPR)。 DPR是通过将视口的CSS像素除以设备的实际屏幕分辨率来计算的。

image.png
image.png

因此,原始Pixel的DPR为2.6,而Pixel 6 Pro的DPR为3.5

Phone 4是第一个DPR大于1的设备,报告的设备像素比为2--屏幕的物理分辨率是逻辑分辨率的两倍。在iPhone 4之前的任何设备的DPR为1:一个逻辑像素对一个物理像素。

如果你在DPR为2的显示器上查看该400像素宽的图像,则每个逻辑像素被呈现在显示器的四个物理像素上:两个水平和两个垂直。图像不会从高密度显示中受益 - 它在DPR为1的显示器上看起来与在DPR为2的显示器上看起来相同。

当然,浏览器渲染引擎绘制的任何内容 - 如文本、CSS形状或SVG - 都将被绘制以适应高密度显示器。但是,从图像格式和压缩中学到的知识,光栅图像是固定的像素网格。尽管可能不总是非常明显,但针对高密度显示放大的光栅图像会与周围页面相比看起来低分辨率。

为了防止这种放大,正在渲染的图像必须具有至少800个像素的固有宽度。当缩小以适应400个逻辑像素宽的布局空间时,该800像素图像源具有双倍的像素密度 - 在具有DPR为2的显示器上,它看起来很清晰。

image.png
image.png

地址:https://codepen.io/web-dot-dev/pen/QWBGVyo

由于DPR为1的显示屏无法利用图像的增加密度,因此图像将被缩小以匹配显示屏。如你所知,缩小的图像看起来也很好。在低密度显示器上,适用于高密度显示器的图像看起来就像任何其他低密度图像。

在《图像和性能》中所学到的,使用缩小到400px的图像源的低密度显示器用户只需要一个固有宽度为400px的源。虽然更大的图像对所有用户来说都可视,但在小型低密度显示屏上渲染的巨大高分辨率图像源将看起来像任何其他小型低密度图像,但速度要慢得多。

具有DPR为1的移动设备非常罕见,尽管在“桌面”浏览环境中仍然很常见。根据Matt Hobbs共享的数据,约18%的GOV.UK浏览会话从2022年11月开始报告DPR为1。虽然高密度图像看起来可能符合这些用户的期望,但它们将产生更高的带宽和处理成本,特别是对于仍然可能拥有低密度显示器的旧设备和较弱设备的用户来说,这是特别令人关注的。

使用srcset可确保只有具有高分辨率显示器的设备接收足够大的图像源以显示清晰,而不会将相同的带宽成本传递给具有低分辨率显示器的用户。

srcset属性标识一个或多个逗号分隔的渲染图像的候选项。每个候选项由两个部分组成:一个URL,就像在src中使用的那样,以及描述该图像源的语法。 srcset中的每个候选项都是由其固有宽度(“w语法”)或预期密度(“x语法”)描述的。

x语法”是“此源适用于具有此密度的显示器”的简写,“2x”后跟的候选项适用于DPR为2的显示器。

代码语言:javascript
复制
<img src="low-density.jpg" srcset="double-density.jpg 2x" alt="...">

支持srcset的浏览器将渲染两个备选项:high-density.jpg,其中2x适用于DPR为2的显示器,以及src属性中的low-density.jpg,如果在srcset中找不到更合适的备选项,则选择该备选项。对于不支持srcset的浏览器,将忽略该属性及其内容,通常会请求src的内容。

很容易将srcset属性中指定的值误解为指令。 2x告知浏览器相关源文件适用于DPR为2的显示器-有关源本身的信息。它不告诉浏览器如何使用该源,只是告知浏览器该源可以如何使用。这是一个微妙但重要的区别:这是一个双倍密度图像,而不是用于双倍密度显示器的图像。

指定“此源适用于2倍显示器”和指定“在2倍显示器上使用此源”之间的语法差异很小,但显示密度只是浏览器用于决定要渲染的备选项的众多相互关联因素之一,其中只有一些你能够知道。

例如:单独地,我们可以确定用户通过prefers-reduced-data媒体查询启用了节省带宽的浏览器偏好设置,并使用它来始终选择低密度图像,而不考虑其显示密度-但除非每个开发人员在每个网站上都一致地实施它,否则对用户来说没有多大用处。他们可能会在一个网站上尊重他们的偏好,并在下一个网站上遇到一个破坏带宽的图像墙。

srcset / sizes使用的故意模糊的资源选择算法为浏览器留出了空间,以决定选择低密度图像以实现带宽下降,或基于最小化数据使用的偏好而选择。我们不需要对如何、何时以及在什么阈值下承担责任。承担浏览器更适合为我们处理的责任和额外工作是没有意义的。

用w来描述宽度

srcset 可以接受第二种类型的描述符,用于图像源候选项。这是一种更加强大的描述符,而且对于我们的目的来说,更容易理解。与标记候选项具有适当尺寸以适应给定显示密度不同,w 语法描述每个候选源的固有宽度。同样,每个候选项都是相同的,除了它们的尺寸 - 相同的内容,相同的裁剪和相同的纵横比。但在这种情况下,你希望用户的浏览器在两个候选项之间进行选择:具有固有宽度为 600pxsmall.jpg,和具有固有宽度为 1200pxlarge.jpg

代码语言:javascript
复制
srcset="small.jpg 600w, large.jpg 1200w"

这并没有告诉浏览器如何处理这些信息 - 只是提供了一个显示图像的候选项列表。在浏览器可以决定渲染哪个源之前,你需要提供更多的信息:一个描述图像在页面上将如何渲染的说明。为此,请使用 sizes 属性。

用 sizes 描述使用情况

在传输图像方面,浏览器表现出极高的性能。对于图像资源的请求将在样式表或 JavaScript 的请求之前启动 - 通常甚至在标记语言被完全解析之前就已经开始了。当浏览器发起这些请求时,除了标记语言之外,它对页面本身没有任何信息 - 它甚至可能尚未启动对外部样式表的请求,更别提应用它们了。在浏览器解析你的标记语言并开始发出外部请求的时候,它只有浏览器级别的信息:用户视口的大小,用户显示器的像素密度,用户偏好等等。

这并没有告诉我们有关图像在页面布局中应该如何渲染的任何信息 - 它甚至不能将视口用作 img 大小的上限的代理,因为它可能占据水平滚动的容器。因此,我们需要使用标记语言提供这些信息给浏览器。对于这些请求,这是我们唯一能够使用的信息。

srcset 一样,sizes 旨在在标记语言解析后尽快提供有关图像的信息。就像 srcset 表示“这里是源文件及其固有大小”,sizes 表示“这里是布局中渲染图像的大小”。描述图像的方式是相对于视口的 - 再次强调,视口大小是浏览器在发出图像请求时拥有的唯一布局信息。

听起来有点复杂,但在实践中理解起来更容易:

代码语言:javascript
复制
<img
 sizes="80vw"
 srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
 src="fallback.jpg"
 alt="...">

这里,sizes 的值告诉浏览器,我们的布局中 img 占用的空间宽度为 80vw - 视口宽度的 80%。记住,这不是一个指令,而是图像在页面布局中的大小的描述。它并没有说“让这个图像占据视口的 80%”,而是“一旦页面渲染完成,这个图像将占据视口的 80%”。

示例:https://codepen.io/web-dot-dev/pen/PoBWLYP

作为开发人员,我们的工作已经完成了。我们已经准确地描述了 srcset 中候选源列表和 sizes 中图像的宽度,就像在 srcset 中的 x 语法一样,剩下的就由浏览器来处理了。

但是为了充分理解这些信息是如何使用的,让我们花点时间来分析用户浏览器在遇到这些标记时做出的决策:

我们告诉浏览器,这个图像将占用可用视口的80%。因此,如果我们在一个宽度为1000像素的设备上渲染这个图像,它将占用800像素。然后,浏览器将把这个值与我们在 srcset 中指定的每个图像源候选项的宽度相除。最小的源具有600像素的固有大小,因此:600÷800 = .75。我们的中等大小的图像宽度为1200像素:1200÷800 = 1.5。我们最大的图像宽度为2000像素:2000÷800 = 2.5

这些计算的结果(.75、1.5和2.5)实际上是专门针对用户视口大小定制的 DPR 选项。由于浏览器还有关于用户显示器密度的信息,因此它做出了一系列决策:

在这个视口大小下,无论用户的显示器密度是多少,都会丢弃 small.jpg 候选源——由于计算出的 DPR 小于1,该源会需要进行任何用户的放大,因此不适用。在一个 DPR 为1的设备上,medium.jpg 提供了最接近的匹配——该源适用于1.5的 DPR 显示,因此它比必要的稍大,但请记住,缩小是一个视觉上无缝的过程。在一个 DPR 为2的设备上,选择 large.jpg 作为最接近的匹配项。

如果同一图像在600像素宽的视口上渲染,所有这些数学计算的结果将完全不同:80vw 现在是480px。当我们把我们的源的宽度除以它时,我们得到1.25、2.54.1666666667。在这个视口大小下,小型small.jpg将在1x设备上选择,而medium.jpg 将在2x设备上匹配。

这张图片在所有浏览上下文中看起来都是相同的:我们的所有源文件除了尺寸之外都完全相同,每一个都会被渲染成用户的显示密度所允许的尽可能锐利的图像。然而,与其为了适应最大的视口和最高密度的显示器向每个用户提供large.jpg,用户将始终获得最小的合适候选项。通过使用描述性语法而不是指令性语法,我们不需要手动设置断点并考虑未来的视口和DPR,只需向浏览器提供信息并允许其为我们确定答案。

由于我们的 sizes 值是相对于视口而完全独立于页面布局的,它增加了一层复杂性。很少有一张图片只占据视口的百分比,没有固定宽度的边距、填充或受页面上其他元素的影响。我们经常需要使用单位的组合来表达图像的宽度;百分比、empx等等。

幸运的是,我们可以在这里使用calc()——任何具有响应式图像本地支持的浏览器也将支持calc(),使我们能够混合和匹配CSS单位——例如,一个占据用户视口的全宽度,减去两侧1em边距的图像:

代码语言:javascript
复制
<img
    sizes="calc(100vw-2em)"
    srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w, x-large.jpg 2400w"
    src="fallback.jpg"
    alt="...">

描述断点

如果你花了很多时间来处理响应式布局,你可能已经注意到这些示例中缺少了一些内容:图像在布局中所占空间很可能会在布局的断点处发生变化。在这种情况下,需要向浏览器传递更多细节:sizes属性接受一组用逗号分隔的候选项,用于指定图像渲染尺寸,就像srcset属性接受一组用逗号分隔的候选项用于指定图像源一样。这些条件使用了熟悉的媒体查询语法。这个语法是第一个匹配:一旦媒体条件匹配,浏览器停止解析sizes属性,然后应用指定的值。

假设你有一张图片,希望在1200像素以上的视口上占据视口宽度的80%,左右各有一个em的内边距,在较小的视口上则占据视口的全部宽度。

代码语言:javascript
复制
  <img
     sizes="(min-width: 1200px) calc(80vw - 2em), 100vw"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

示例:https://codepen.io/web-dot-dev/pen/RwBoYRx

如果用户的视口大于1200pxcalc(80vw - 2em)描述了我们布局中图片的宽度。如果(min-width: 1200px)条件不匹配,浏览器就会转到下一个值。因为没有一个特定的媒体条件与这个值相联系,所以100vw被作为默认值使用。如果你使用max-width媒体查询来编写这个sizes属性:”

代码语言:javascript
复制
  <img
     sizes="(max-width: 1200px) 100vw, calc(80vw - 2em)"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

示例:https://codepen.io/web-dot-dev/pen/BaPQOzO

简单来说,(max-width: 1200px)是否匹配?如果不匹配,继续。下一个值 calc(80vw - 2em)没有限定条件,因此这是被选中的。

现在,我们已经向浏览器提供了关于图像元素的所有这些信息 - 潜在来源、内在宽度以及打算向用户渲染图像的方式 - 浏览器使用模糊的规则来确定如何处理这些信息。

在HTML规范中编码的源选择算法在选择源的方式上是明确模糊的。一旦源、它们的描述符和图像的渲染方式都被解析了,浏览器就可以自由地做任何它想做的事情,我们不能确定浏览器会选择哪个源。

一种语法,它说“在高分辨率显示器上使用此源”,可能是可预测的,但它不会解决响应式布局中图像的核心问题:保留用户带宽。屏幕像素密度只与互联网连接速度有较弱的相关性,如果有的话。如果你使用顶级笔记本电脑,但通过计量连接、通过你的手机连接或使用不稳定的飞机WiFi连接浏览网络,你可能想选择低分辨率的图像源,无论你的显示器质量如何。

把最终决定留给浏览器允许进行比我们通过严格的预定语法进行的性能改进更多的性能改进。例如:在大多数浏览器中,使用srcsetsizes语法的img永远不会请求比用户已经在浏览器缓存中拥有的源更小的尺寸的源。当浏览器可以无缝地缩小它已经拥有的图像源时,为什么要为一个看起来相同的源发出新请求呢?但是,如果用户将其视口缩放到需要新图像才能避免缩放的程度,那么仍将进行该请求,以便一切看起来符合我们的期望。

虽然这种缺乏明确控制权可能听起来有点可怕,但是因为我们正在使用具有相同内容的源文件,与浏览器的决策无关,我们不太可能向用户渲染“破碎”的体验,就像单源src一样。

使用 sizes 和srcset

信息量有点多了。srcsetsizes都是密集的语法,用相对较少的字符描述了大量信息。也就是说,无论好坏如何,这是经过设计的:使这些语法不那么简洁,更容易被我们人类解析,可能会使它们更难被浏览器解析。字符串中添加的复杂性越多,就越有可能出现解析器错误或不同浏览器之间行为意外不同的情况。然而,这里有一个好处:对机器来说更容易阅读的语法对它们来说也更容易编写。

对于srcset来说,这是一个明确的自动化案例。很少有人会手工制作多个版本的图像以用于生产环境,而是使用类似Gulp这样的任务运行器、Webpack这样的捆绑器、第三方CDN(如Cloudinary)或已经内置在您选择的CMS中的功能来自动化该过程。只要提供足够的信息来生成我们的资源,系统就有足够的信息将它们写入可行的srcset属性中。

对于sizes来说,自动化要困难一些。系统计算图像在渲染布局中的大小的唯一方法是已渲染布局。幸运的是,出现了许多开发人员工具,将手写sizes属性的过程抽象化,效率远远超过手动编写。例如,respImageLint是一段代码片段,旨在审核我们的sizes属性是否准确,并提供改进建议。

Lazysizes 项目通过推迟图像请求直到布局建立后,允许JavaScript为我们生成sizes值,以效率为代价实现了一些速度。如果你正在使用完全客户端党建框架(如React或Vue),则有许多解决方案可用于编写和/或生成srcsetsizes属性,我们将在CMS和框架中进一步讨论这些解决方案。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用 x 描述密度
  • 用w来描述宽度
  • 用 sizes 描述使用情况
    • 描述断点
      • 使用 sizes 和srcset
      相关产品与服务
      内容分发网络 CDN
      内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档