具有辅助功能和打印机支持的延迟加载图像

Lazy loading images with accessibility and printer support

我正在寻找一种在不损害可打印性和可访问性且不引入布局转换(内容跳转)的情况下实现图像延迟加载的正确方法,最好使用本机 loading=lazy 和旧浏览器的回退。问题 使用 JavaScript 延迟加载图像如何工作? 包含各种解决方案 none,其中完全满足所有这些要求。

优雅的解决方案应该基于有效且完整的 html 标记,即使用 <img srcsrcsetsizeswidthheightloading 属性,而不是像流行的 javascript 库 lazysizes and vanilla-lazyload 那样将数据放入 data- 属性中。应该也不需要使用 <noscript> 元素。

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

上面提到的两个 javascript 库都需要完全没有任何 src 属性的无效标记,或者空的或低质量的占位符 (LQIP),而 src 数据是而是放入 data-src,并将 srcset 数据放入 data-srcset,所有这些仅适用于 javascript。这在 2020 年是否被认为是可接受的甚至是最佳实践,这是否不会损害网站可访问性、跨设备兼容性或搜索引擎优化?

更新:

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

Houssein Djirdeh 在 lazy-load-with-print-ctl1l4wu1.now.sh using javascript to change loading=lazy to loading=eager when a "print" button is clicked. The same function could also be used onbeforeprint 提供了另一种解决方法。

我做了一个codepen using lazysizes.

我又做了一个 codepen using vanilla-lazyload .

我想分叉一个 javascript 解决方案以使其使用 srcsrcset 工作,但这可能以前已经尝试过,权衡是一旦延迟加载脚本开始作用于图像元素,浏览器可能已经开始下载源文件。

@Ingo Steinke 在仔细研究您提出的问题的答案之前,必须回过头来思考为什么会出现延迟加载,以及它作为思想框架在启动时解决了哪些损害。关键词思想框架......它不是解决方案,我会继续说它从来都不是解决方案,而是思想框架。 为什么我们想要它:

  • 尽量减少从服务器获取不必要的文件 - 如果 运行拥有大量用户,这对带宽至关重要。原来是互联网版的工业生产中的just in time
  • 在 JS/HTML 普及 async 和 defer 之前的旧版浏览器版本,与浏览器的交互 window 在加载所有内容之前一直受到阻碍。
  • 现在我们所知道的宽带在真正意义上的方式和渗透方面仅在过去 6-7 年才出现。我们想要它是因为我们不想在低带宽上遇到 no.2。老实说,缩小和压缩 JS 和 CSS 文件的关注度和意识形态一直在增长——所有这些都是因为应该尽量减少往返服务器的往返行程,以便可以获取列表中的下一项。请记住,浏览器倾向于将每个 window 或活动 window 的同时下载连接限制在 6 个左右。 Google 推广 3 秒规则是有原因的。如果上面是让 运行 照原样运行,那么 3 秒规则将落在它的头上,就好像它没有腿一样。
  • 于是出现了思想框架。
  • 图像作为 CSS 背景:这是因为它没有扰乱页面的视觉效果。一切都保持原样,然后突然变得五颜六色。那个时代的网页似乎具有弹性贴合,即曾经充满空气的袋子突然弹出 - 变成了跳跃的城堡。作为前端开发人员,这越来越成为一个坏主意。因此,固定容器的高度和 运行 图像作为背景有帮助,HTML5 背景对齐属性相应地自行升级。甚至还有变体,并且仍然在使用多个背景时使用,其中一个正在加载 spiril 或低端模糊图像版本,在其上获取实际预期图像。由于降级免费将在单个下载实例中被获取并填充到任何地方,因此它创造了更令人愉悦的视觉效果,并且用户知道会发生什么。即使没有下载预期的图像,也可以打印。
  • 然后通过 data-src 劫持 DOM 出现了它的 JS 版本,无效的图像标签删除了 src,等等。仅在内容滚动到时触发更改。显然会有滞后,但这可以通过在 JS 中实现的 CSS 方法或计算滚动点并触发事件几个像素来抵消。他们都在同一个前提下工作。

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

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

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

给我看看你那丑陋的代码,我不想看!

如果你不想读我的文章最后一节“演示”包含一个fiddle你可以调查(在代码中评论得相当好)说明。

或者有 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> 元素中添加断点,而是说我们想要一个 427px 宽的图像(例如)。

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

首先,如果您更改站点上的所有断点都没有关系,因此无需更新任何地方的图片元素。

其次 320px 和 400px 宽的图像在 kb 方面的差异超过 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>

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

如果您不关心 Layout Shift,您甚至可以省略 widthheight 属性,但由于 Cumulative Layout Shift (CLS) 是一个 Core Web Vital 我不会推荐一下。

辅助功能

图像只是标准图像,alt 属性被继承。

我什至添加了一个额外的检查,如果 alt 属性为空/缺失,则通过 CSS class.

将大红色边框添加到图像中

问题/妥协

如果页面已经滚动则布局切换

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

辅助功能

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

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

IE

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

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

原创 虽然这没有在 Firefox、Safari 等中测试。如果有问题,很容易在那里工作。

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

这对于可访问性来说很重要,因为很多屏幕 reader 用户都依赖 IE,因为它与 JAWS 配合得很好。

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

检查延迟

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

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

这需要彻底重新考虑,但在我处理其他部分时它现在就可以了。

演示

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

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

或者如前所述,可以在我控制的 https://inhu.co/so/image-concept.php.

域上更轻松地查看和测试演示

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

2022 年可打印延迟加载的正确解决方案是使用本机 loading 属性。

<img loading=lazy>

使用自定义打印按钮的建议已被废弃,因为 chromium issue 875403 已得到修复。

之前的建议包括 adding a custom print button(在使用本机浏览器打印功能时没有解决问题)或使用 JavaScript 加载图像 onBeforePrint 后者不被认为是好的解决方案,作为loading=lazy,作为“DOM-only”解决方案,一定不能依赖JavaScript.

请注意,即使在错误修复之后,您的一些用户仍可能使用有问题的浏览器版本访问您的网站。