具有辅助功能和打印机支持的延迟加载图像 不浪费图像字节最小标记和语义正确的标记WebP打印没有布局变化假设要延迟加载的内容在页面加载时在屏幕外无需 JavaScript 和干净的标记即可工作如果页面已经滚动,则布局移位可访问性浏览器检查延迟

问题描述

我正在寻找一种正确的方法来实现图像的延迟加载,而不会损害可打印性和可访问性,并且不会引入布局转换(内容跳转),最好使用原生 loading=lazy 和旧浏览器的回退。问题 How lazy loading images using JavaScript works? 的答案包括各种解决方案,但没有一个完全满足所有这些要求。

优雅的解决方案应该基于有效且完整的 html 标记,即使用 <img srcsrcsetsizeswidthheight 和 { {1}} 属性,而不是像流行的 javascript 库 lazysizesvanilla-lazyload 那样将数据放入 loading 属性中。也不应该使用 data- 元素。

由于第一个支持原生延迟加载的浏览器 bug in chrome,尚未加载的图像将在打印页面中丢失。

上面提到的两个 javascript 库都需要没有任何 <noscript> 属性的无效标记,或者一个空的或低质量的占位符 (LQIP),而 src 数据被放入 {{1}取而代之的是,src 数据放入 data-src,所有这些都只适用于 javascript。这是否被认为是 2020 年可接受的甚至最佳实践,这是否既不损害网站可访问性、跨设备兼容性,也不损害搜索引擎优化

更新:

我尝试仅使用 HTML 和 CSS srcset 背景图像 in this codepen解决打印错误。即使这按预期工作,每个图像都会有一个必要的 css 指令,这既不优雅也不通用。不幸的是,也无法在 data-srcset 元素内使用媒体查询

Houssein Djirdeh 在 lazy-load-with-print-ctl1l4wu1.now.sh 的另一个解决方法是在单击“打印”按钮时使用 javascript 将 @media print 更改为 <picture>。也可以使用相同的函数 onbeforeprint

我做了一个codepen using lazysizes

我又做了一个 codepen using vanilla-lazyload

我考虑过使用 loading=lazyloading=eager 来创建一个 javascript 解决方案来使其工作,但这可能之前肯定已经尝试过,权衡是一旦延迟加载脚本开始对图像元素,浏览器可能已经开始下载源文件

解决方法

给我看看你那可怕的代码,我不想读!

如果您不想阅读我的散文最后一部分“演示”包含一个小提琴,您可以使用说明进行调查(在代码中注释得相当好)。

如果您想使用它,也可以使用 link to the demo on a domain I control here that is easier to test against

还有一个 version that nearly works in IE here,出于某种原因,“准备打印”屏幕在打印前没有消失,但所有其他功能都可以工作(令人惊讶)!

要尝试的事情:

  1. 尝试在不同的浏览器尺寸下查看请求的动态图像
  2. 在较慢的连接上尝试并检查网络选项卡以查看延迟加载的操作以及延迟加载工作方式的动态变化(取决于连接速度)。
  3. 尝试在网络连接较慢时按 CTRL + P(不滚动页面)以查看我们如何在打印前加载尚未在 DOM 中的图像
  4. 尝试在网络连接较慢的情况下加载页面,然后使用 FILE > PRINT 来查看我们如何处理在这种情况下尚未加载的图像。

0.1 版,概念验证

所以还有很长的路要走,但我想到目前为止我会分享我的解决方案。

它很复杂(并且有缺陷),但它大约是您所要求的 90%,并且可能是比当前图像延迟加载更好的解决方案。

此外,我在为一个想法制作原型时不擅长编写干净的 JS。 我只能向你们中的任何一个勇敢地在这个阶段尝试和理解我的代码的人道歉

仅在 chrome 中测试 - 因此您可以想象它可能无法在其他浏览器中工作,尤其是在抓取 <noscript> 标签的内容是出了名的不一致时。但是最终我希望这将是一个生产就绪的解决方案。

最后,在此阶段构建 API 的工作量太大,因此我使用 https://placehold.it 来调整图像大小 - 因此需要删除几行冗余代码。

主要特点/优势

不浪费图像字节

此解决方案计算要请求的图像的实际大小。因此,与其在 <picture> 元素中添加断点,不如说我们想要一个 427 像素宽的图像(例如)。

这显然需要一个服务器端图像大小调整解决方案(这超出了堆栈溢出答案的范围),但好处是巨大的。

首先,如果您更改了站点上的所有断点,则无关紧要,因此无需在任何地方更新图片元素。

其次就 kb 而言,320 像素和 400 像素宽的图像之间的差异超过 40%,因此选择“类似大小”的图像并不理想(这基本上是 <picture>元素)。

第三,如果人们(像我一样)拥有庞大的 4K 显示器和不错的连接速度,那么您实际上可以为他们提供 4K 图像(尽管连接速度检测是我需要在 0.2 版中进行的改进)。

第四,如果图像在一个屏幕尺寸上是其父容器的 50% 宽度,在另一个屏幕上是其父容器的 25% 宽度,但是容器在一个屏幕尺寸下是 60% 屏幕宽度,在一个屏幕尺寸下是 80% 屏幕宽度另一个。

试图在 <picture> 元素中正确处理这一点充其量只会令人沮丧。如果您随后决定更改布局,则情况会更糟,因为您必须重新计算所有宽度百分比等。

最后,这可以节省制作页面的时间/可以很好地与 CMS 配合使用,因为您不需要教别人如何在图像上设置断点(因为我还没有看到 CMS 比仅仅设置断点处理得更好就好像每个图像在屏幕上都是全宽一样)。

最小标记(和语义正确的标记)

虽然您不想使用 <noscript> 并避免使用 data 属性,但我需要同时使用这两个属性。

然而,您编写/生成的标记实际上是一个 <img> 元素,您通常将其包装在 <noscript> 标签中。

一旦图像完全加载,所有杂乱都会被移除,因此您的 DOM 只剩下一个 <img> 元素。

如果您想更换解决方案(如果浏览器技术改进等),那么在 <noscripts> 上进行简单的更换即可让您获得一个标准的 HTML 标记,随时可以改进。

WebP

当然,如果支持,此解决方案会请求 WebP 图像(一切都与性能有关!)。在服务器端,您需要相应地处理这些(例如,如果图像是具有透明度的 PNG,即使请求 WebP 图像,您也将其发回)。

打印

哦,这很有趣!

如果我们发送要打印的文档并且图像尚未加载,我们无能为力,我尝试了各种技巧(例如设置背景图像),但这是不可能的(或者我不聪明足以解决它......更有可能!)

所以我所做的是考虑现实世界的场景并尽可能优雅地覆盖它们。

  1. 如果用户使用快速连接,我们会延迟加载图像,但不会等待滚动来执行此操作。这可能意味着我们服务器上的负载会增加一些,但我认为打印非常重要(仅次于速度)。
  2. 如果用户的连接速度很慢,那么我们使用传统的延迟加载。
  3. 如果他们按下 CTRL + P,我们会拦截打印命令并在加载图像时显示一条消息。这个概念是 taken from the example OP gave by Houssein Djirdeh,但使用我们的延迟加载机制。
  4. 如果用户使用 FILE > PRINT 打印,那么我们会为尚未加载的图像显示占位符,说明他们需要滚动页面才能显示图像。 (占位符的大小与图片大致相同)。

这是我目前能想到的最好的妥协。

没有布局变化(假设要延迟加载的内容在页面加载时在屏幕外)。

不是 100% 完美的解决方案,但由于“首屏”内容不应该延迟加载,并且 95% 的页面访问从页面顶部开始,这是一个合理的妥协。

我们使用一个空白 SVG(以正确的比例“即时”创建),使用 data URI 作为图像的占位符,然后在需要加载图像时交换 src .这避免了网络请求并确保在图像加载时没有布局偏移。

这也意味着页面在任何时候都是语义正确的,没有空的 hrefs

如果用户已经滚动页面然后重新加载,则会发生布局转换。这是因为 <img> 元素是通过 JavaScript 创建的(除非禁用了 JavaScript,在这种情况下,图像会从图像的 <noscript> 版本显示)。所以它们在解析时不存在于 DOM 中。

这是可以避免的,但需要在其他地方做出妥协,所以我现在认为这是可以接受的。

无需 JavaScript 和干净的标记即可工作

原始标记只是 <noscript> 标签内的一个图像。没有自定义标记或 data-attributes

我使用的标记是:

<noscript class="lazy">
    <img src="https://placehold.it/1500x500" alt="an image" width="1500px" height="500px"/>
</noscript>

它并没有变得更加标准和干净,如果您不在其他地方使用 class="lazy" 标签,它甚至不需要 <noscript>,它纯粹是为了碰撞。>

如果您不关心布局偏移,您甚至可以省略 widthheight 属性,但由于累积布局偏移 (CLS) 是 Core Web Vital 我不推荐它.

可访问性

图像只是标准图像,并带有 alt 属性。

我什至添加了一个额外的检查,如果 alt 属性为空/缺少一个大的红色边框,则会通过 CSS 类添加到图像中。

问题/妥协

如果页面已经滚动,则布局移位

如前所述,如果页面已经滚动,则会出现大量布局变化,类似于将标准图像添加到没有 widthheight 属性的页面。

可访问性

虽然图像解决方案本身是可访问的,但按 CTRL + P 时出现的屏幕不是。这纯粹是我的懒惰,一旦有了更最终的解决方案,就很容易解决。

缺乏 Internet Explorer 支持(见下文)是一个很大的可访问性问题。

浏览器

更新 有一个 version that nearly works in IE11 here。我正在调查是否可以让它一直工作到 IE9。

也在 Firefox、Edge 和 Safari(移动)中进行了测试,似乎可以在那里工作。

原件 虽然这未在 Firefox、Safari 等中进行测试。如果出现问题,可以轻松地在那里工作。

然而,在 IE 和其他旧浏览器中访问 <noscript> 标签的内容是出了名的困难(并且在某些版本中是不可能的),因此该解决方案可能永远不会在 IE 中工作。

这在可访问性方面很重要,因为许多屏幕阅读器用户依赖 IE,因为它与 JAWS 配合良好。

我想到的解决方案是在服务器上使用用户代理嗅探并提供不同的标记和 JavaScript,但这很复杂且非常小众,因此我不会在本答案中这样做。

检查延迟

我正在使用一种相当粗略的方法来检查下载小图像两次并测量加载时间的延迟(尝试猜测是否有人使用 3G/4G 连接)。

在尝试获得最高性能时,2 个不需要的网络请求并不理想(不是因为我下载了 100字节,而是因为在初始化之前高延迟连接的延迟)。>

这需要彻底重新思考,但现在我会在其他方面工作。

演示

由于 30,000 个字符的字符数限制,无法使用内联小提琴!

这里是当前的 JS Fiddle - https://jsfiddle.net/9d5qs6ba/

或者,如前所述,可以在我在 https://inhu.co/so/image-concept.php 控制的域上更轻松地查看和测试演示。

我知道这不是链接到您自己的域的“完成的事情”,但是很难在 jsfiddle 等上测试打印。

,

@Ingo Steinke 在回答你所提出的问题之前,你必须回顾一下为什么会出现延迟加载,以及它在启动时作为思想框架解决了哪些损害。思想的关键词框架……它不是一个解决方案,我会继续说它从来都不是一个解决方案,而是一个思想框架。 为什么我们想要它:

  • 尽量减少从服务器获取不必要的文件 - 如果用户群很大,这对带宽至关重要。所以它是工业生产中的及时互联网版本。
  • 传统浏览器版本以及异步和延迟在 JS/HTML 中流行之前,与浏览器窗口的交互仍然受到阻碍,直到所有内容都加载完毕。
  • 现在,我们所知道的宽带只是在过去 6-7 年才出现,真正意义上的方式和渗透。我们想要它是因为我们不想在低带宽上遇到 no.2。老实说,对于缩小和压缩 JS 和 CSS 文件的担忧和意识形态一直在增长,而且仍然越来越受到关注——这一切都是因为应该最小化到服务器和返回的往返行程,以便可以获取列表中的下一个项目。请记住,浏览器倾向于将每个窗口或活动窗口的同时下载连接限制在大约 6 个。 Google 推广 3 秒规则是有原因的。如果上面让它继续运行,那么 3 秒规则就会像它没有腿一样落在它的头上。
  • 于是出现了思想框架。
  • 图像作为 CSS 背景:这是因为它没有弄乱页面的视觉方面。一切都保持原样,然后突然变得丰富多彩。是时候让网页看起来有弹性了,也就是那个曾经充满空气的袋子突然弹出,变成了跳跃的城堡。作为前端开发人员,这越来越成为坏主意。因此,固定高度和容器,然后将图像作为背景运行,HTML5 背景对齐属性相应地自行升级。甚至还有变体并且仍然使用多个背景,其中一个是加载 spiril 或低端模糊图像版本,在其上获取实际预期图像。由于将在单个下载实例中获取和填充水平下降的 bacground,因此它创建了更令人愉悦的视觉效果,并且用户知道会发生什么。即使没有下载预期的图像,也可以用于打印。
  • 然后通过 data-src 劫持 DOM 来了它的 JS 版本,无效的图像标签删除了 src,等等。仅在内容滚动到时触发更改。显然会有延迟,但这要么是通过在 JS 中实现的 CSS 方法来解决的,要么是计算滚动点并触发前面几个像素的事件。他们仍然在同一个前提下工作。

有一个问题需要被问到,您已经在借口中触及了它......它没有控制或改变浏览器本机功能。浏览器可能会在您的脚本与任何事情有任何关系之前获取该项目。

这是这里的主要问题。 BOM 不关心甚至想关心你的脚本要求做什么,它知道是否有一个 src 属性来获取内容。没有任何解决方案改变了这一点。如果我们可以更改该功能,那么认为框架将成为解决方案。

我仍然相信浏览器不应该仅仅为了它而改变它,因此从未在辩论中获得跟踪。浏览器所做的是被称为推测或前瞻预解析器的预取,这是浏览器中值得称赞的最大改进。就像我们在每个字符串浏览器的地址栏中输入 url 一样,即使我没有输入整个 url,也会预取内容。我专门开发了一个程序,我可以从这些前瞻预解析器中获取服务器上接收到的任何内容。大多数情况下,只需不到一秒钟的时间就可以得到响应,浏览器开始处理包括图像和 JS 在内的所有内容。这与 No.1 和 No.2 中讨论的生涩延迟弹性俯卧展示相反。然而,它并没有减少服务器命中率。我们以任何方式进行延迟加载的原因。但是一些 JS 解决方法获得了吸引力,因为没有 src 属性,因此预解析器不会获取图像,并且只有在用户实际发送到页面并触发事件时才会这样做。一些浏览器玩弄了自己延迟加载它们的想法,但如果它没有假设标准的普遍一致性,那就放手吧。 通用标准很简单,如果有 src 属性浏览器将获取项目 no if 和 buts。想象一下,如果情况并非如此,那么糟糕的前端开发人员就会陷入困境。 因此,您在辩论中提出的问题是关于 BOM 功能的问题,正如我上面所讨论的。没有解决方法。在您的情况下,用于屏幕和打印版本的显示。如何确保在发送打印命令时加载图像。事实证明,BOM 打印的答案很简单。事实 ebing 屏幕显示和之前的事实是在 BOM/DOM 传播级别之前的一切。同样,你无法改变这一点。 所以你必须做出权衡。权衡将以另一种思想框架的形式出现。而不是假设一切都准备好打印,而是根据用户命令打印准备好。有一个 div 弹出并显示文档的打印版本,然后从那里打印。 UI 可以是任何只需要一秒钟左右的东西,因为大部分内容将以任何方式加载,其余的将花费很短的时间。在这方面,用于打印的 CSS 规则可能非常方便。您几乎可以在互联网上的许多地方看到它的运行情况。 结论正如我们今天所站的那样,我们使用 BOM 功能将屏幕显示和打印显示与延迟加载捆绑在一起,这不是延迟加载的目的,因此没有提供比单纯的黑客更好的解决方案。因此,您必须根据将两者分开的上下文来创建您的 UI,以使其正常工作。