[译]如何构建自己的Progressive Image Loader

news/2024/7/3 12:08:03

你可以在Facebook和Medium上遇到过渐进式图片,当页面滚动到视图时,模糊的低分辨率图像会被清晰的全分辨率版本替换。

图片描述

预览图片非常小(也许是20px宽的高压缩JPEG格式),该文件可以小于300字节,并立即出现快速加载的模糊轮廓,当需要的时候,会通过延迟加载的形式加载真实图像。

渐进式图像非常伟大,但是目前的解决方案比较复杂。幸运的是,我们可以用HTML5/CSS3/JavaScript构建一个示例,示例代码:

  • 快速轻量级——只需463字节的CSS和1007字节的JavaScript(经压缩)

  • 支持响应式图片加载更大或更高分辨率(Retina)屏幕的替代版本

  • 没有依赖——可与任何框架工作

  • 兼容所有现代浏览器(IE10+)

  • 在旧版浏览器中,或是当JavaScript/图片加载失败时,会渐进增强

  • 易于使用

我们的演示和GitHub代码

这是我们的demo示例
Download the code from GitHub

HTML代码

我们会以一些基础HTML来实现渐进式图片:

<a href="full.jpg" class="progressive replace">
  <img src="tiny.jpg" class="preview" alt="image" />
</a>

此处:

  • full.jpg 是清晰的大分辨率图片,地址在href

  • tiny.jpg 是轻量的预览图片

我们已经有一个小型工作系统了,没有任何的JavaScript(也许在旧的浏览器中会失效),用户可以通过点击预览完整的图片。

两个图片必须有相同的宽高比,例如,如果full.jpg是800*200,则其最终的宽高比是4:1,那么tiny.jpg可以是4:1,但是不能使用30px的宽度,因为那样高度将会是一个分数而不是7.5px。

注意链接和预览图片上使用到的classes,这些会被我们运用到JavaScript中。

内联或外联图片

预览图片可以以data URL的形式内联,例如:

<img src="https://img-blog.csdnimg.cn/2022010710285399066.jpeg"  class="preview" />

内联图片会立即显示,需要较少的HTTP请求,并避免额外的页面回流,但是:

  • 他需要更多资源来添加或更改内联图片(虽然构建时候, 可以借助如Gulp的帮助)

  • base-64编码效率较低,通常比二进制数据大30%(尽管这被额外的HTTP请求头抵消了)

  • 内联图片无法缓存在本地,它们只能在HTML页面中缓存,如果不提出相同的请求,它们不能在另一个页面使用

  • HTTP/2减少了对内联图片的需求

比较实用的是:如果内联图片只在单个页面使用,并且所需代码量很小(如URL比较短),内联图片是一个不错的选择。

CSS

我们先来定义container样式:

a.progressive {
  position: relative;
  display: block;
  overflow: hidden;
  outline: none;
}

这是设置container容器的布局属性,如果有需要,link可以应用其他类和样式设置尺寸或位置。

你可以考虑使用精确的尺寸或使用padding-top来强制实现固有的宽高比,这能确保在容器进行尺寸调整前避免图片加载的负载和回流,不过这要计算每个图像的大小和宽高比。我选择比较简单的方式:

  • 预览和大图必须具有相同的宽高比(见上文)

  • 预览图片将几乎立即定义容器的高度,因为它是内联的,或是快速加载的

再次提示:如果你在一个包含大量图片的网页上,定义了容器固定的宽度和高度,效果会更好,例如一个图库(所有的图片都可能具有相同的宽高比)。

当完整的大图加载完成并且点击事件停止时,容器上的'replace'类将被删除,因此,我们可以删除标准链接指针。

a.progressive:not(.replace) {
  cursor: default;
}

容器中的预览图和大图根据容器的宽度调整大小:

a.progressive img {
  display: block;
  width: 100%;
  max-width: none;
  height: auto;
  border: 0 none;
}

注意height:auto是必须的,IE10/11可能会在计算图像高度的时候出错。

预览图像使用2vw的长度模糊,确保模糊后看起来有相似的轮廓,而与页面大小无关。在container中应用overflow: hidden可为容器提供一个硬边缘。它也缩放1.05倍,防止通过图片的模糊外边缘看到图片的背景颜色。这表示我们可以使用令人愉快的缩放效果来显示完整的图像。

a.progressive img.preview {
  filter: blur(2vw);
  transform: scale(1.05);
}

最后,我们定义图片完整显示时候的样式和动画:

a.progressive img.reveal {
  position: absolute;
  left: 0;
  top: 0;
  will-change: transform, opacity;
  animation: reveal 1s ease-out;
}

@keyframes reveal {
  0% {transform: scale(1.05); opacity: 0;}
  100% {transform: scale(1); opacity: 1;}
}

大图位于预览图上方,在1秒内,不透明度从0增加到1,刻度从1.05变为1。你可以根据自己的需要增加其他的转换/过滤效果。

JavaScript

我们遵循的是渐进增强模式,所以JavaScript代码最初会向页面添加load事件监听器之前检查所需浏览器的API是否可用。

// progressive-image.js
if (window.addEventListener && window.requestAnimationFrame && document.getElementsByClassName) window.addEventListener('load', function() {

当页面和所有资源加载完成时,将触发load事件。我们不希望大型图片在基本资源(如字体、CSS、JavaScript和预览图片)加载完成之前加载(这可能发生:我们使用DOMContentLoaded事件——当DOM准备就绪时触发的事件)。

接下来,我们获取获取类名为progressivereplace的所有图像容器元素:

var pItem = document.getElementsByClassName('progressive replace'), timer;

getElementsByClassName()返回一个活动的类数组HTMLCollection,匹配到的元素将从页面中添加或删除。它的好处是很快就会显示出来。

接下来,我么将定义一个inView()函数,该函数通过其getBoundingClientRect()方法和window.pageYOffset垂直滚动位置进行比较,从而确定容器是否在视图内。

// image in view?
function inView() {
  var wT = window.pageYOffset, wB = wT + window.innerHeight, cRect, pT, pB, p = 0;
  while (p < pItem.length) {

    cRect = pItem[p].getBoundingClientRect();
    pT = wT + cRect.top;
    pB = pT + cRect.height;

    if (wT < pB && wB > pT) {
      loadFullImage(pItem[p]);
      pItem[p].classList.remove('replace');
    }
    else p++;
  }
}

当容器在视图中时,它的节点会被传递到loadFullImage()函数内,并且replace类会被删除(会立即从pItem HTMLCollection中删除节点,因此容器不会被再次重新处理)。

loadFullImage()函数创建了一个新的HTML Image()对象,并根据需要设置其值,即将容器的href的值复制到src属性中,并应用一个reveal类。

// replace with full image
function loadFullImage(item) {
  if (!item || !item.href) return;

  // load image
  var img = new Image();
  if (item.dataset) {
    img.srcset = item.dataset.srcset || '';
    img.sizes = item.dataset.sizes || '';
  }
  img.src = item.href;
  img.className = 'reveal';
  if (img.complete) addImg();
  else img.onload = addImg;

加载图片后调用内部的addImg函数:

// replace image
  function addImg() {
    // disable click
    item.addEventListener('click', function(e) { e.preventDefault(); }, false);

    // add full image
    item.appendChild(img).addEventListener('animationend', function(e) {
      // remove preview image
      var pImg = item.querySelector && item.querySelector('img.preview');
      
      if (pImg) {
        e.target.alt = pImg.alt || '';
        item.removeChild(pImg);
        e.target.classList.remove('reveal');
      }
    });
  }
}

注意:

  • 禁用容器上的点击事件

  • 将图像附加到开始淡出淡入/缩放动画的页面

  • 使用animationend监听器等待动画结束,然后复制alt
    内容。删除预览图片节点,并从完整图片中删除reveal类。这一步有助于提高性能,并且防止在调整Edge浏览器大小时出现一些奇怪的剪切问题。

最后,我们必须调用inView()函数来检查所有的渐进式图片容器在首次运行时是否在页面上可见。

inView();

我们还必须在滚动页面或调整浏览器大小时调用函数,在一些旧的浏览器(主要指IE)可以非常迅速地对这些事件作出回应,所以我们需要限制回调,以确保它不能在300毫秒内被再一次调用。

window.addEventListener('scroll', scroller, false);
window.addEventListener('resize', scroller, false);

function scroller(e) {
  timer = timer || setTimeout(function() {
    timer = null;
    requestAnimationFrame(inView);
  }, 300);
}

注意,对requestAnimationFrame的调用,它将在下一次重绘inView函数。

响应式图片

HTML 中 image的srcsetsizes属性定义了不同大小和分辨率的多个图像,浏览器可以为设备选择最合适的版本。

上面的代码支持这个功能——添加data-srcsetdata-sizes属性到link容器,例如:

<a href="small.jpg"
  data-srcset="small.jpg 800w, large.jpg 1200w"
  data-sizes="100vw"
  class="progressive replace">
  <img src="preview.jpg" class="preview" alt="image" />
</a>

加载完成后,完整的图片代码将是:

<img src="small.jpg"
    srcset="small.jpg 800w, large.jpg 1200w"
    sizes="100vw"
    alt="image" />

当视图窗口宽度为800px或更高时,现代浏览器将加载large.jpg,旧版浏览器和视图窗口较小的浏览器会加载small.jpg。详细信息,请查阅 How to Build Responsive Images with srcset

使用笔记

我会保持代码的轻量性,并且可以随时使用和易于改进,有待优化的地方有:

  • 水平方向的滚动检查。目前只检查垂直方向的滚动

  • 动态添加渐进式图像。使用JavaScript动态添加的渐进式图像只有在发生滚动或调整大小事件发生时才会被替换

  • Firefox性能。浏览器在替换大图片时可能会遇到困难(你也许可以看到明显的闪烁)

作者:Craig Buckler
原文:How to Build Your Own Progressive Image Loader


http://www.niftyadmin.cn/n/3094179.html

相关文章

最新關於域名解析錯誤的有效方法

因特網用戶往往使用域名來訪問網站&#xff0c;系統必須將域名轉換成IP地址&#xff0c;才能正確通過各級路由器&#xff0c;訪問到正確的網站內容。域名的轉換工作&#xff0c;壹般是由DNS&#xff08;域名解析伺服器&#xff09;來完成。但有時用戶會發現&#xff0c;輸入正確…

OPENCV获取像素点

int offsetImg y*step x*channels; uchar *dataImg; dataImg(uchar *)image1->imageData;int bdataImg[offsetImg];int gdataImg[offsetImg1];int rdataImg[offsetImg2];还有另外一种方法 bcvGet2D(image1,y,x).val[0];gcvGet2D(image1,y,x).val[1];rcvGet2D(image1,y,x)…

[译]Godot系列教程二 - 场景实例化(Instancing)

场景实例化(Instancing) 原理阐述 创建一个场景并将节点扔到里面对于小项目是适用的&#xff0c;但随着项目不断发展&#xff0c;用到越来越多的节点&#xff0c;整个项目很快就会演化成难以管理的状态。 为了解决这个问题&#xff0c;Godot允许一个项目分割成多个场景。这一点…

视频图像处理------人头识别

人头识别思路 1.查找资料&#xff0c;获知&#xff0c;可以利用轮廓特征进行人头识别的方法。 提取区域轮廓之前&#xff0c;需要对图像做一些预处理。首先&#xff0c;用原始图像和参考背景图像相减&#xff0c;然后用模糊C均值算法求得的灰度阀值对其进行二值化。 2.在硕士论…

EF-CodeFirst数据库迁移时可能出现的几种错误- The EntityFramework package is not installed on project 'MovieEF'...

相关&#xff1a;EF-CodeFirst实现过程数据库迁移 enable-Migrations -Force Add-Migration demo 最后再次执行&#xff1a;Update-Database -Force codefirst在做数据库迁移的可能出现的错误&#xff1a; ** 1. The EntityFramework package is not installed on project Movi…

如何快速产生流量,流量精灵使用方法

1流量意义&#xff1a;流量就是浏览量&#xff0c;刷流量一方面可以增加自己的排名&#xff0c;另一方面当用户看到自己的宝贝浏览量很大的时候会觉得这个东西卖的很火&#xff0c;如果浏览量只有十几甚至几&#xff0c;则不会产生购买欲望。 "累计流量"&#xff0c…

Exchange中限制部分用户外网访问

最近遇到一个需求&#xff0c;公司某业务部门需要让本部门一部分员工不能通过公网使用Exchange邮件系统。然后&#xff0c;公司邮件系统是发布公网使用的&#xff0c;要直接限制部分员工不能外网访问有一定的困难&#xff0c;经过讨论想到了两个解决方案。第一个方案&#xff0…