[譯]如何構建本身的Progressive Image Loader

你能夠在FacebookMedium上遇到過漸進式圖片,當頁面滾動到視圖時,模糊的低分辨率圖像會被清晰的全分辨率版本替換。ios

圖片描述

預覽圖片很是小(也許是20px寬的高壓縮JPEG格式),該文件能夠小於300字節,並當即出現快速加載的模糊輪廓,當須要的時候,會經過延遲加載的形式加載真實圖像。git

漸進式圖像很是偉大,可是目前的解決方案比較複雜。幸運的是,咱們能夠用HTML5/CSS3/JavaScript構建一個示例,示例代碼:github

  • 快速輕量級——只需463字節的CSS和1007字節的JavaScript(經壓縮)web

  • 支持響應式圖片加載更大或更高分辨率(Retina)屏幕的替代版本數組

  • 沒有依賴——可與任何框架工做瀏覽器

  • 兼容全部現代瀏覽器(IE10+)緩存

  • 在舊版瀏覽器中,或是當JavaScript/圖片加載失敗時,會漸進加強app

  • 易於使用框架

咱們的演示和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="data:image/jpeg;base64,/9j/4AAQSkZJ..."  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

相關文章
相關標籤/搜索