基於vue的一種圖片載入狀態效果的實現
做的一個需求,其中有一個是實現類似於下圖的一個圖片上傳效果:
從本地上傳圖片到伺服器,然後伺服器響應返回這個圖片在伺服器上的連結地址,將這個連結地址所對應的圖片顯示到螢幕上,並且在此圖片資源完全下載下來之前,呈現一個動態 loading
的展點陣圖,直到圖片完全下載後進行替換
小tip
這是個很常見的需求,關鍵點在於檢測圖片的載入完成事件,一般的做法是,先將 img
標籤的 src
屬性指向一個當圖片載入完成後,再將 src
指向真正的連結即可
const img = new Image()
// 真實圖片地址
const realSrc = 'http://a.com/real.png'
img.src = realSrc
img.onload = () => {
// domImgElement 為 img 的 HTML DOM物件
domImgElement.src = realSrc
}
這種做法是沒什麼問題的,大多數情況下也都是這麼做的,不過由於我擅(xian)長(de)思(dan)考(teng),然後就想到一個小問題
我手頭的這個需求是基於 vue
,在頁面渲染完畢之後,再想改變頁面上的內容,就需要重新渲染一遍頁面,儘管 vue
存在 vnode
的概念,更新速度很快,但就算再快都還是要把整個頁面刷一遍的,這個過程無論如何是跑不掉的,如果頁面上存在十幾張甚至幾十張類似於上面那種圖片非同步載入效果,那豈不是要改變幾十次 data
vue
內部就要對應 patch
幾十次,然後頁面也就跟著整個更新幾十次,想想都覺得可怕
每多 patch
一次 vnode tree
,每多更新一次頁面,都將引起 CPU
和 GPU
的能耗升高,日積月累四捨五入那就是一個龐大而隱形的能源消耗黑洞,對個人的發展、對公司的財務、對地球的溫室效應都將是一個沉重的負擔!
更何況,一般這種列表型別的圖片資料,都是存在於一個數組中,每當有圖片下載完畢,在替換 src
之前,還要先根據某個標誌位,例如圖片 id
或者 url
來從這個圖片陣列中找到對應的圖片,然後才能進行替換,而且這一步還要注意vue
不自動對巢狀物件的屬性進行響應式操作的問題:
loadImg ( imgId) {
const img = new Image()
// 真實圖片地址
const realSrc = 'http://a.com/real.png'
img.src = realSrc
img.onload = () => {
this.imgList.some(imgObj => {
if (imgObj.id === imgId) {
imgObj.src = realSrc
this.$forceUpdate()
return true
}
return false
})
}
}
這只是一個小需求而已,為什麼要搞得這麼複雜?於是我決定最好簡單一點,不要那麼依賴 js
,最好將大部分的工作交給 css
來完成,因為相比之下,css
的消耗更低
關鍵就是不進行替換 img
標籤 src
的操作,這樣一下子就能節省大半的 js
操作,以下述 DOM
結構為例:
<div class="img-item">
<img :src="item.realUrl" alt="" />
</div>
item.realUrl
就是當前圖片的真實地址,拿到就直接賦值上去,後續也不會再操作 src
這個屬性了,因為大部分工作移到了 css
上:
.img-item {
position: relative;
width: 80px;
height: 80px;
background-color: #aeaeae;
}
.img-item::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 22px;
height: 22px;
margin-left: -11px;
margin-top: -11px;
/* 這是載入效果的圖片 */
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABAUlEQVQ4T6WSzyrEYRSGn2crF6GmJJTsLLGyEVM2lAshdriUsbAwsWeWlpJ/Kck1KNtXM330G8b84due8z7fed9z5J/P3/RJNoCDUt9XT3r1/gAkmVSfktwB00V0r84MBCS5AJaAc6A2EiDJOPBW+WUb2KtaSDKr3lYn6bKQ5AxYBS7V5WHy/QIkWSmCV/VhGHG7pwNIsg6cFlFdbfbZzlRHqI9VwCbQKKIt9bgPYKEArqqAMWCriBrq+0gWPpuTzAG7wKF68x2SpKY+99tC2/sa0FTr1cYkE8ALMK9ef9a+r7F9RDvAkdpK0ip+F0vY/SfoMXIXYOApDxvcrxn8BfABIiRjEYfmQAcAAAAASUVORK5CYII=') no-repeat;
background-position: center center;
background-size: contain;
animation: loadpic .5s infinite linear;
z-index: 1;
}
@keyframes loadpic {
from { transform: rotate(0) }
to { transform: rotate(360deg) }
}
img {
position: relative;
height: 100%;
width: 100%;
z-index: 2;
}
.img-item
是包裹單個 img
的父元素,它的作用有兩個:
第一,撐開佔據的空間
第二,用它的 ::before
偽元素來載入用於標識圖片正在載入的動效 loading
圖,也就是說,將 loading
圖放到了 ::before
上
::before
會一直存在,直到 .box
內的 img
資源下載完畢後顯示出來,將其遮蓋住,因為 img
元素的寬高與 .box
相同,並且 img
的 z-index
更大,所以只要 img
沒有載入完畢,那麼就不會顯示,那麼就會顯示 ::before
元素,也就是顯示 loading
狀態,而只要 img
載入完畢了,那麼就會顯示出來,就會遮蓋掉 ::before
,這個過程視覺上看起來就能達到文章開頭那個圖片上的效果,而且消耗更低
emmmm
,既然說到了圖片,那麼再順便說個關於 img
標籤不太常用但有時候會比較有用的功能點吧
對於一張圖片:
<img src="http://a.com/b.png" />
如果 src
的指向沒有對應的資源或者不合法,那麼在瀏覽器上將會呈現一種特殊的樣式,例如:
不同的瀏覽器對於無效圖片的顯示可能也不一樣,在我的 Chrome
上,就是上述這種破碎圖片的樣子,就是告訴你圖片找不到了,這種使用者體驗肯定是不太好的,對於這種情況,大部分的做法就是監聽 img
標籤的 onerror
事件,如果觸發了此事件,則說明失敗,然後就可以走失敗的邏輯了
不過這也是 js
的方法,每個圖片都要設定一個監聽事件,那麼如果頁面上有幾十個圖片,豈不是要設定幾十個監聽事件?
每多設定一個監聽事件,都將引起…
這只是一個漸進增強的體驗,不應該投入那麼多的資源,於是需要再次藉助 css
的能力,不監聽 error
事件了,直接給需要載入失敗就友好顯示的圖片加上如下樣式:
img:before {
/* 關鍵是這句 */
content: '圖片載入失敗~';
/* 下面都是輔助的樣式程式碼 */
display: block;
padding: 0 20px;
background-color: #eaeaea;
color: indianred;
line-height: 30px;
}
如果圖片載入失敗,那麼原先應該顯示破碎樣式的地方,將會顯示 圖片載入失敗~
的提示文案
主要就是利用了 :before
和 :after
這兩個偽元素在 img
標籤上的特性,img
正常載入,則這兩個偽元素的內容就不顯示,如果 img
載入失敗,就顯示這兩個偽元素的內容,這兩個偽元素的能力還是很多的,上面只是我隨便舉的一個例子
總結
我覺得 css
對於前端工程師來說也是一項很重要的能力,別的不說,減少了 js
的程式碼量,自然也就能減少報錯的概率,有時候還可以很輕鬆地實現一些需要一大堆 js
才能實現的效果,提升頁面的效能
所以我有時候看到有的前端說 ta
根本不 care
CSS
,認為這東西連程式語言都算不上,語法規範更是亂七八糟,學都懶得學,ta
只需要 JS
就能寫出任何效果來,特別是在如今各種 js
框架大行其道的當下,很多初學者更是隻知 js
而不知 css
,甚至連 html
都用不好,不僅是初學者,就算是一些工作了好些年的前端工程師,看到他們寫的程式碼的時候,我有時候也會驚歎於他們的 css
和 html
程式碼居然還可以這麼寫
大多數情況下這種現象或許也沒什麼毛病,畢竟屁股決定腦袋,就算是出去面試,面試官也幾乎只會問 js
方面的問題,而不會問你 section
和 article
標籤適合在哪些情況下使用,字型的基線是怎麼回事,line-height
又有什麼作用,我只是很納悶,作為一名前端開發工程師,前端三劍客只會其一,悲哀或許談不上,但三種技能開局就丟掉了倆,最起碼可以算是自毀功力了吧
這篇文章似乎有點水…突然想寫就寫了…