本文將詳細介紹前端項目中的圖片相關的優化方案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不支持半透明,也不支持動畫
【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只能在安卓系統下使用
通常地,在對設計圖進行修改前,首先要保留一份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格式的圖片,它具備更優的圖像數據壓縮算法,能帶來更小的圖片體積,同等畫面質量下,體積比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均可以根據設備和瀏覽器進行尺寸調整、裁剪和肯定最合適的格式,甚至更多 —— 壓縮、檢測像素密度、水印、人臉識別和容許後期處理。藉助這些強大的功能和可以將參數附到 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-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')">