圖片優化

前面的話

  本文將詳細介紹前端項目中的圖片相關的優化方案html

 

圖片格式

  目前在前端的開發中經常使用的圖片格式有jpg、png、gif,png八、png2四、png3二、svg和webp前端

【gif】vue

  gif是無損的,具備文件小、支持動畫及透明的優勢。但gif沒法支持半透明,且僅支持8bit的索引色,即在整個圖片中,只能存在256種不一樣的顏色nginx

  但實際上,gif是一種逐漸被拋棄的圖片格式。png格式的出現就是爲了替代它web

  因爲gif支持動畫的這個「一招鮮」的本領,在網絡中仍然佔有一席之地,主要用於一些小圖標ajax

【jpg】算法

  jpg又稱爲jpeg,是有損的,但採用了直接色,保證了色彩的豐富性。jpg圖片支持透明和半透明,全部空白區域填充白色npm

  jpg格式主要用於高清圖、攝影圖等大圖canvas

【png8】數組

  png8是無損的,是png的索引色版本

  前面提到過,png是gif格式的替代者,在相同圖片效果下,png8具備更小的文件體積,且支持透明度的調節

  但png8不支持半透明,也不支持動畫

png

【png24】

  png24是無損的,是png的直接色版本。 

  png24支持透明,也支持半透明,但png有文件體積較大的缺點

  png24的目標是替換jpg。但通常而言,png24的文件大小是jpg的5倍之多,但顯示效果卻只有一點點提高

【png32】

  png32是在png24的基礎上,添加了8位的alpha通道信息,能夠支持透明和半透明,且支持圖層,輔助線等複雜數據的保存

  使用ps導出的透明的png24圖片,其實是閹割版的png32,由於只有32位的png圖片才支持透明,閹割版是說導出的圖片不支持圖層

【SVG】

  svg是無損的矢量圖。svg與上面這些圖片格式最大的不一樣是,上面的圖片格式都是位圖,而svg是矢量圖,具備不管如何縮放都不會失真的優勢

  svg格式很是適用於繪製logo、圖標等  

  但因爲低版本瀏覽器支持不足,應用不普遍

【webp】

  WebP 格式是 Google 於2010年發佈的一種支持有損壓縮和無損壓縮的圖片文件格式,派生自圖像編碼格式 VP8。它具備較優的圖像數據壓縮算法,能帶來更小的圖片體積,並且擁有肉眼識別無差別的圖像質量,同時具有了無損和有損的壓縮模式、Alpha 透明以及動畫的特性,在 JPEG 和 PNG 上的轉化效果都很是優秀、穩定和統一。目前,知名網站 Youtube 、Facebook、Ebay 等均有使用 WebP格式。

  WebP 集合了多種圖片文件格式的特色,JPEG 適合壓縮照片和其餘細節豐富的圖片,GIF 能夠顯示動態圖片,PNG 支持透明圖像,圖片色彩很是豐富,而 WebP 則兼具上述優勢,且較於它們還有更出色的地方。

  無損壓縮後的 WebP 比 PNG 文件少了 45% 的文件大小,即便 PNG 文件通過其餘壓縮工具壓縮後,WebP 仍是能夠減小 28% 的文件大小。此外,與 JPEG 相比,在質量相同的狀況下,WebP 格式圖像的體積要比 JPEG 格式圖像小 40%,而 WebP 在壓縮方面比 JPEG 格式更優越

  但目前爲止,webp只能在安卓系統下使用

 

PS保存

  通常地,在對設計圖進行修改前,首先要保留一份psd源文本,而後再在其副本上進行修改

  經過photoshop將設計圖切成須要的素材時,涉及到圖片格式的設置問題,應注意如下幾點:

  一、當圖片色彩豐富且無透明要求時,建議保存爲jpg格式並選擇合適的品質,通常爲60-80

  二、當圖片色彩不太豐富時不管有無透明要求,保存爲PNG8格式(特色是隻有256種顏色,文件自己比較小),保存時選擇無仿色,無雜邊

  三、當圖片有半透明要求時,保存PNG24格式(對圖片不進行壓縮,全部相對比較大)

 

懶加載

  圖片延遲加載也稱爲懶加載,延遲加載圖片或符合某些條件時才加載某些圖片,一般用於圖片比較多的網頁。能夠減小請求數或者延遲請求數,優化性能

【呈現形式】

  通常而言,有如下三種呈現形式

  一、延時加載,使用setTimeout或setInterval進行加載延遲,若是用戶在加載前就離開,天然就不會進行加載

  二、條件加載,符合某些條件或者觸發了某些條件纔開始異步加載

  三、可視區域加載,僅僅加載用戶能夠看到的區域,這個主要監控滾動條實現,通常距離用戶看到的底邊很近的時候開始加載,這樣能保證用戶下拉時圖片正好接上,不會有太長時間的停頓

【基本步驟】

  一、待加載的圖片默認加載一張佔位圖

  二、使用data-src屬性保存真正地址

  三、當觸發某些條件時,自動改變該區域的圖片的src屬性爲真實的地址

【可視區域加載】

  可視區域加載,是圖片懶加載最經常使用的一種形式,涉及到的細節以下所示:

  一、判斷可視區域

  圖片頂部距離頁面頂部的高度小於頁面高度

  二、保存圖片路徑

  待加載的圖片默認加載一張佔位圖,使用data-src屬性保存真正的地址

  三、判斷加載時機

  監聽頁面的scroll事件,收集當前進入頁面的圖片元素,給src賦值爲真正的地址,給已加載的圖片添加標記

  四、滾動性能提高

  使用函數節流優化滾動性能

  代碼以下所示:

const oList = document.getElementById('list')
const viewHeight = oList.clientHeight
const eles = document.querySelectorAll('img[data-src]')
const lazyLoad = () => {
  Array.prototype.forEach.call(eles, item => {
    const rect = item.getBoundingClientRect()
    if (rect.top <= viewHeight && !item.isLoaded) {
      item.isLoaded = true
      const oImg = new Image()
      oImg.onload = () => { item.src = oImg.src }
      oImg.src = item.getAttribute('data-src')
    }
  })
}
const throttle = (fn, wait=100) =>{
  return function() {
    if(fn.timer) return
    fn.timer = setTimeout(() => {
      fn.apply(this, arguments)
      fn.timer = null
    }, wait)
  }
}
lazyLoad()
oList.addEventListener('scroll', throttle(lazyLoad))

  效果以下

 

懶加載進階

  上面代碼的問題在於,每次調用getBoundingClientRect()方法時,都會觸發迴流,嚴重地影響性能

  可使用Intersection Observer這一API來解決問題,能夠異步觀察目標元素與祖先元素或頂層文件的交集變化

  建立一個 IntersectionObserver對象並傳入相應參數和回調用函數,該回調函數將會在target 元素和root的交集大小超過threshold規定的大小時候被執行

var options = {
    root: document.querySelector('#scrollArea'), rootMargin: '0px', threshold: 1.0 } var callback = function(entries, observer) { /* Content excerpted, show below */ }; var observer = new IntersectionObserver(callback, options);

  若是root參數指定爲null或者不指定的時候默認使用瀏覽器視口作爲root

  rootMargin表示root元素的外邊距。該屬性值是用做root元素和target發生交集時的計算交集的區域範圍,使用該屬性能夠控制root元素每一邊的收縮或者擴張。默認值爲0

  threshold能夠是單一的number也能夠是number數組,target元素和root元素相交程度達到該值的時候,將會被執行

  若是隻是想要探測當target元素的在root元素中的可見性超過50%的時候,能夠指定該屬性值爲0.5。若是想要target元素在root元素的可見程度每多25%就執行一次回調,那麼能夠指定一個數組[0, 0.25, 0.5, 0.75, 1]。默認值是0(意味着只要有一個target像素出如今root元素中,回調函數將會被執行)。該值爲1.0含義是當target徹底出如今root元素時回調纔會被執行

  爲每一個觀察者配置一個目標

var target = document.querySelector('#listItem')
observer.observe(target)

  當目標知足該IntersectionObserver指定的threshold值,回調被調用

var callback = function(entries, observer) { 
    entries.forEach(entry => { entry.time; entry.rootBounds; entry.boundingClientRect; entry.intersectionRect; entry.intersectionRatio; entry.target;  }); };

  time: 可見性發生變化的時間,是一個高精度時間戳,單位爲毫秒

  intersectionRatio: 目標元素的可見比例,即 intersectionRect 佔 boundingClientRect 的比例,徹底可見時爲 1 ,徹底不可見時小於等於 0

  boundingClientRect: 目標元素的矩形區域的信息

  intersectionRect: 目標元素與視口(或根元素)的交叉區域的信息

  rootBounds: 根元素的矩形區域的信息,getBoundingClientRect() 方法的返回值,若是沒有根元素(即直接相對於視口滾動),則返回 null

  isIntersecting: 是否進入了視口,boolean 值

  target: 被觀察的目標元素,是一個 DOM 節點對象

  代碼以下所示:

const eles = document.querySelectorAll('img[data-src]')
const observer = new IntersectionObserver( entries => {
  entries.forEach(entry => {
    if (entry.intersectionRatio > 0) {
      let oImg = entry.target
      oImg.src = oImg.getAttribute('data-src')
      observer.unobserve(oImg)
    }
  })
}, {
  root: document.getElementById('list')
})
eles.forEach(item => { observer.observe(item) })

 

預加載

  預加載圖片是提高用戶體驗的一個好辦法,提早加載用戶所需的圖片,保證圖片快速、無縫發佈,使用戶在瀏覽器網站時得到更好用戶體驗。經常使用於圖片畫廊等應用中

【使用場景】

  如下幾個場景中,可使用圖片預加載

  一、在首屏加載以前,縮短白屏時間

  二、在空閒時間爲SPA的下一屏預加載

  三、預測用戶操做,預先加載數據

【三種思路】

  通常來講,實現預加載有三種思路:

  一、使用頁面無用元素的背景圖片來進行圖片預加載

<button>載入圖片</button>
<img src="img/test.png" alt="測試">
<ul class="list">
    <li id="preload1"></li>
    <li id="preload2"></li>
    <li id="preload3"></li>
    <li id="preload4"></li>
</ul>
<script>
var oBtn = document.getElementsByTagName('button')[0];
var oImg0 = document.images[0]; var array = ["img/img1.gif","img/img2.gif","img/img3.gif","img/img4.gif"] var iNow = -1; oBtn.onclick = function(){ iNow++; iNow = iNow%4; oImg0.src = array[iNow]; } function preLoadImg(){ preload1.style.background = "url('img/img1.gif')"; preload2.style.background = "url('img/img2.gif')"; preload3.style.background = "url('img/img3.gif')"; preload4.style.background = "url('img/img4.gif')"; } window.onload = function(){ preLoadImg(); } </script>

  二、經過new Image()或document.createElement('img')建立img標籤,而後經過img的src屬性來加載圖片

<button>載入圖片</button>
<img src="img/test.png" alt="測試">
<script>
var oBtn = document.getElementsByTagName('button')[0];
var oImg0 = document.images[0]; var array = ["img/img1.gif","img/img2.gif","img/img3.gif","img/img4.gif"] var iNow = -1; oBtn.onclick = function(){ iNow++; iNow = iNow%4; oImg0.src = array[iNow]; } var aImages = []; function preLoadImg(array){ for(var i = 0, len = preLoadImg.arguments[0].length; i < len; i++){ aImages[i] = new Image(); aImages[i].src = preLoadImg.arguments[0][i]; } } window.onload = function(){ preLoadImg(array); } </script>

  三、經過XHR對象發送ajax請求來獲取圖片,但只能獲取同域圖片

【onload和onerror】

  經過添加onload和onerror這兩個事件鉤子,能夠實現圖片在加載完成和加載失敗時的函數回調。多個資源加載能夠計算出大致進度,如3/10

<button>載入圖片</button>
<img src="img/test.png" alt="測試">
<script>
var oBtn = document.getElementsByTagName('button')[0];
var oImg0 = document.images[0]; var array = ["img/img1.gif","img/img2.gif","img/img3.gif","img/img4.gif"] var iNow = -1; oBtn.onclick = function(){ iNow++; iNow = iNow%4; oImg0.src = array[iNow]; } var iDown = 0; var oImage = new Image(); function preLoadImg(arr){ function loadImgTest(arr){ iDown++; if(iDown < arr.length){ preLoadImg(arr); }else{ alert('ok'); oImg.onload = null; oImg = null; } } oImage.onload = function(){ loadImgTest(arr); }; oImage.onerror = function(){ loadImgTest(arr); }; oImage.src = arr[iDown]; } preLoadImg(array); </script>

  將預加載寫成一個通用的資源加載器,代碼以下

let isFunc = function(f){
  return typeof f === 'function' } function resLoader(config){ this.option = { resourceType: 'image', baseUrl: '', resources: [], onStart: null, onProgress: null, onComplete: null } if(config){ for(i in config){ this.options[i] = config[i] } } else { alert('參數錯誤') return } // 加載器狀態 0:未啓動 1:正在加載 2:加載完畢 this.status = 0 this.total = this.option.resources.length || 0 this.currentIndex = 0 } resLoader.prototype.start = function(){ this.status = 1 let _this = this let baseUrl = this.option.baseUrl for(var i = 0, l = this.option.resources.length; i < l; i++){ let r = this.option.resources[i], url = '' if(r.indexOf('http://) === 0 || r.indexOf('https://') === 0){ url = r } else { url = baseUrl + r } let image = new Image() image.onload = function(){_this.loaded()} image.onerror = function(){_this.loaded()} image.src = url } if(isFunc(this.option.onStart)){ this.option.onStart(this.total) } } resloader.prototype.loaded = funtion(){ if(isFunc(this.option.onProgress)){ this.option.onProgress(++this.currentIndex, this.total) } if(this.currentIndex === this.total){ if(isFunc(this.option.onComplete)){ this.option.onComplete(this.total) } } } let loader = new resLoader({ resources: ['img1.png','img2.png','img3.png'], onStart: function(total){ console.log('start:' + total) }, onProgress: function(current, total){ console.log(current+ '/' + total) let percent = current/total*100 }, onComplete: function(total){ console.log('加載完畢:' + total + '個資源') } }) loader.start()

 

Webp

  在安卓下可使用webp格式的圖片,它具備更優的圖像數據壓縮算法,能帶來更小的圖片體積,同等畫面質量下,體積比jpg、png少了25%以上,並且同時具有了無損和有損的壓縮模式、Alpha 透明以及動畫的特性

【檢測】

  是否支持webp格式的圖片的檢測方法以下

const isSupportWebp = !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0

【七牛自動轉換】

  七牛支持自動將其餘格式的圖片轉換成webp格式的圖片,只需添加在圖片地址以後添加?imageView2/2/format/webp

  下面是詳細代碼

/**
 * 若該瀏覽器支持webp格式,則將返回webp圖片的url,不然返回原url
 * @param {string} 'https://static.xiaohuochai.site/20180612030117.png'
 * @return {string} 'https://static.xiaohuochai.site/20180612030117.png?imageView2/1/format/webp'
 */
export const getUrlWithWebp = url => {
  const isSupportWebp = !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0
  if (isSupportWebp) {
    return `${url}?imageView2/2/format/webp`
  }
  return url
}

【pageSpeed】

  Google開發的PageSpeed模塊有一個功能,會自動將圖像轉換成WebP格式或者是瀏覽器所支持的其它格式

  以nginx爲例,它的設置很簡單

  一、在http模塊開啓pagespeed屬性

pagespeed on;
pagespeed FileCachePath "/var/cache/ngx_pagespeed/";

  二、在主機配置添加以下一行代碼,就能啓用這個特性

pagespeed EnableFilters convert_png_to_jpeg,convert_jpeg_to_webp;

 

CDN

  圖片性能的最後一步就是分發了。全部資源均可以從使用 CDN 中受益

  CDN 能夠下降從圖片站點提供自適應和高性能圖片的複雜度。大多數CDN均可以根據設備和瀏覽器進行尺寸調整、裁剪和肯定最合適的格式,甚至更多 —— 壓縮、檢測像素密度、水印、人臉識別和容許後期處理。藉助這些強大的功能和可以將參數附到 URL 中,使得提供以用戶爲中心的圖片變得垂手可得了

  以七牛云爲例,imageView2 提供簡單快捷的圖片格式轉換、縮略、剪裁功能。只須要填寫幾個參數,便可對圖片進行縮略操做,生成各類縮略圖

// 裁剪正中部分,等比縮小生成200x200縮略圖
http://odum9helk.qnssl.com/resource/gogopher.jpg?imageView2/1/w/200/h/200

// 寬度固定爲200px,高度等比縮小,生成200x133縮略圖
http://odum9helk.qnssl.com/resource/gogopher.jpg?imageView2/2/w/200

 

Vue圖片優化

  下面來介紹一個VUE下的插件vue-lazyload,能夠實現圖片或背景圖片的懶加載、使用webp圖片等效果

  首先,使用npm安裝

npm install vue-lazyload -D

【基礎使用】

  在main.js中,使用該插件

import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload)

// or with options
Vue.use(VueLazyload, {
  preLoad: 1.3,
  error: 'dist/error.png',
  loading: 'dist/loading.gif',
  attempt: 1
})

new Vue({
  el: 'body',
  components: {
    App
  }
})

  在模板中使用v-lazy來保存圖片的真實地址

<ul>
  <li v-for="img in list">
    <img v-lazy="img.src" >
  </li>
</ul>

  或者使用v-lazy-container配合圖片的data-src屬性

<div v-lazy-container="{ selector: 'img', error: 'xxx.jpg', loading: 'xxx.jpg' }">
  <img data-src="//domain.com/img1.jpg">
  <img data-src="//domain.com/img2.jpg">
  <img data-src="//domain.com/img3.jpg">  
</div>
<div v-lazy-container="{ selector: 'img' }">
  <img data-src="//domain.com/img1.jpg" data-error="xxx.jpg">
  <img data-src="//domain.com/img2.jpg" data-loading="xxx.jpg">
  <img data-src="//domain.com/img3.jpg">  
</div>

【參數說明】

  vue-lazyload相關配置的參數說明

key       描述    默認值    類型
preLoad    預加載的寬高比    1.3    Number
error      圖片加載失敗時使用的圖片源    'data-src'    String
loading    圖片加載的路徑    'data-src'    String
attempt    嘗試加載次數    3    Number
listenEvents    想讓vue監聽的事件    ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove']    
adapter    動態修改元素屬性    { }    
filter     圖像的SRC過濾器    { }    
lazyComponent    懶加載組件    false    

  好比,可使用以下的配置

Vue.use(VueLazyload, {
  preLoad: 1.3,
  error: 'dist/error.png',
  loading: 'dist/loading.gif',
  attempt: 1,
  listenEvents: [ 'scroll' ]
})

【動態修改圖片的URL】

Vue.use(vueLazy, {
    filter: {
      progressive (listener, options) {
          const isCDN = /qiniudn.com/
          if (isCDN.test(listener.src)) {
              listener.el.setAttribute('lazy-progressive', 'true')
              listener.loading = listener.src + '?imageView2/1/w/10/h/10'
          }
      },
      webp (listener, options) {
          if (!options.supportWebp) return
          const isCDN = /qiniudn.com/
          if (isCDN.test(listener.src)) {
              listener.src += '?imageView2/2/format/webp'
          }
      }
    }
})

【設置事件鉤子】

Vue.use(vueLazy, {
    adapter: {
        loaded ({ bindType, el, naturalHeight, naturalWidth, $parent, src, loading, error, Init }) {
            // do something here
            // example for call LoadedHandler
            LoadedHandler(el)
        },
        loading (listender, Init) {
            console.log('loading')
        },
        error (listender, Init) {
            console.log('error')
        }
    }
})

【使用IntersectionObserver】

Vue.use(vueLazy, {
  // set observer to true
  observer: true,

  // optional
  observerOptions: {
    rootMargin: '0px',
    threshold: 0.1
  }
})

【懶加載組件】

Vue.use(VueLazyload, {
  lazyComponent: true
});
<lazy-component @show="handler">
  <img class="mini-cover" :src="img.src" width="100%" height="400">
</lazy-component>

<script>
  {
    ...
    methods: {
      handler (component) {
        console.log('this component is showing')
      }
    }

  }
</script>

【組件中爲圖片或背景圖片使用懶加載】

<script>
export default {
  data () {
    return {
      imgObj: {
        src: 'http://xx.com/logo.png',
        error: 'http://xx.com/error.png',
        loading: 'http://xx.com/loading-spin.svg'
      },
      imgUrl: 'http://xx.com/logo.png' // String
    }
  }
}
</script>

<template>
  <div ref="container">
     <img v-lazy="imgUrl"/>
     <div v-lazy:background-image="imgUrl"></div>

     <!-- with customer error and loading -->
     <img v-lazy="imgObj"/>
     <div v-lazy:background-image="imgObj"></div>

     <!-- Customer scrollable element -->
     <img v-lazy.container ="imgUrl"/>
     <div v-lazy:background-image.container="img"></div>

    <!-- srcset -->
    <img v-lazy="'img.400px.jpg'" data-srcset="img.400px.jpg 400w, img.800px.jpg 800w, img.1200px.jpg 1200w">
    <img v-lazy="imgUrl" :data-srcset="imgUrl' + '?size=400 400w, ' + imgUrl + ' ?size=800 800w, ' + imgUrl +'/1200.jpg 1200w'" />
  </div>
</template>

【CSS狀態】

<img src="imgUrl" lazy="loading">
<img src="imgUrl" lazy="loaded">
<img src="imgUrl" lazy="error">
<style>
  img[lazy=loading] {
    /*your style here*/
  }
  img[lazy=error] {
    /*your style here*/
  }
  img[lazy=loaded] {
    /*your style here*/
  }
  /*
  or background-image
  */
  .yourclass[lazy=loading] {
    /*your style here*/
  }
  .yourclass[lazy=error] {
    /*your style here*/
  }
  .yourclass[lazy=loaded] {
    /*your style here*/
  }
</style>

  下面是前端小站中vue-lazyload插件的使用

// main.js
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  loading: require('./assets/imgs/loading.gif'),
  listenEvents: ['scroll'],
  filter: {
    webp(listener, options) {
      if (!options.supportWebp) return
      const isCDN = /xiaohuochai.site/
      if (isCDN.test(listener.src)) {
        listener.src += '?imageView2/2/format/webp'
      }
    }
  }
})
// homeCategory.vue
<ul v-lazy:background-image="require('@/assets/imgs/match-bg.jpg')">
相關文章
相關標籤/搜索