淘寶的新Sprite方法——使用Img Sprite技術對按鈕加載順序優化的簡單研究

導言

話說web前端的技術先驅,國外當屬Yahoo,而國內則當屬淘寶。所以它們的一舉一動都是整個互聯網的風向標。

就在前幾天,偶然發現曾經一直困擾自己的鼠標懸停共用部分空間的問題在淘寶這裏有瞭解決方案,馬上拿來分析下。

懸停狀態共用某一區域

用語言描述總是怪怪的,還是上圖吧。

001

可以看到,按照正常的的導航製作方法,每個按鈕元素都有自己的獨立區域,它們之間互不影響。但在本例中,兩個按鈕卻在懸停狀態下共用了中間一條線的區域,它是如何實現的,我們一步步來分析。先看它的背景原圖(其它部分不重要,無視之)

sprites

從原圖上看,與我們傳統的Sprite並無區別。再來看看Html結構

?
1
2
3
4
5
6
/*這是我自己寫的,與淘寶原始結構稍有不同*/
<ul>
    <li class="nav1"><a href="#">1</a></li>
    <li class="nav2"><a href="#">2</a></li>
    <li class="nav3"><a href="#">3</a></li>
</ul>

此結構與正常結構並無區別,繼續用普通方法定義CSS

?
1
2
3
4
5
6
7
8
li {float:left; margin:0; padding:0;}
li a {float:left; display:inline; height:38px; text-indent:-10000px; overflow:hidden; background:url('sprites.gif') no-repeat;}
li.nav1 a {width:103px; background-position:0 -19px;}
li.nav1 a:hover {background-position:0 -57px;}
li.nav2 a {width:91px; background-position:-103px -19px;}
li.nav2 a:hover {background-position:-103px -57px;}
li.nav3 a {width:106px; background-position:-194px -19px;}
li.nav3 a:hover {background-position:-194px -57px;}

效果如下

002

可以看出,當鼠標懸停在中間按鈕時,並沒有實現我們想要的效果,這當然也是在意料之中的。繼續分析,我們希望鼠標懸停在中間按鈕時按鈕空間可以佔據右側豎線的一像素,這時想到當:hover時,元素可以使margin-left負一個像素,這樣中間按鈕就向左偏移了一個像素,爲了使它的物理大小不發生變化,再給它定義一個像素的padding-left,這樣該按鈕在邏輯上增大了一個像素,物理上卻沒有變化,通過修改CSS,讓:hover下背景定位向右偏一個像素

?
1
2
3
4
5
/*修改過的CSS*/
li a:hover {margin-left:-1px; padding-left:1px;}
li.nav1 a:hover {background-position:1px -57px;}
li.nav2 a:hover {background-position:-102px -57px;}
li.nav3 a:hover {background-position:-193px -57px;}

效果如下

003

這正是我們要的效果。經測試,發現進行負值操作後IE6下面出現bug

004

乍一看,我們的css沒有生效,其實不然,爲了解決此bug,按鈕元素需要加上position:relative;

?
1
li a {position:relative;float:left; display:inline; height:38px; text-indent:-10000px; overflow:hidden; background:url('sprites.gif') no-repeat;}

這樣,此bug就已經被修復了,IE6下顯示正常。(對此bug的產生原因並沒有深入研究,請高手補充)

到此,我們需要的效果就已經實現了。

等等!如果僅僅只是到此,這不過是一個小技巧而已,並沒有什麼。其實好戲纔剛剛開始!

淘寶新的Sprite解決方案

在研究淘寶代碼的過程中,發現它用了一個怪異的文檔結構,如下圖

005

可以看到,它沒有用我們常用的<li class="nav1"><a href="#">1</a></li>方式製作按鈕,而是採用了img圖片,src中的地址正是用來做Sprite的背景圖片。就在我對此百思不得其解的時候,猛然想起曾經看過的一篇關於瀏覽器對圖片加載順序的文章,我恍然大悟,難道它這麼做是與此有關?

補充,如果稱傳統Sprite技術爲CSS Sprite的話,該技術則爲IMG Sprite

馬上進行測試,文檔結構如下

?
1
2
3
4
5
6
<ul>
    <li class="nav1"><a href="#">1</a></li>
    <li class="nav2"><a href="#">2</a></li>
    <li class="nav3"><a href="#">3</a></li>
</ul>
<img src="37.jpg" />

結果如下

006

結果果然如預期所料,由於瀏覽器渲染時認爲img爲內容,而background只是修飾,所以在加載時,瀏覽器會先加載img圖片,而最後才加載background的圖片。瀏覽器這樣認爲,從邏輯上來講是對的,但在實際運用中,我們往往會把導航做爲最重要的部分,而且希望它能夠最快的加載出來。由於瀏覽器的這個特性,我們往往不得不接受在加載大量img圖片之後纔看到導航緩緩出現,如果background在導航中僅僅只是修飾作用還好,如果像此例般,描述性文字是存在於圖片中,繼而讓瀏覽者面長時間對空白等待,這就不可接受了。

經過上述分析,再回過頭來重新看淘寶的結構,便能明白他這樣做的良苦用心。

按照淘寶的風格,重新定義文檔結構如下

?
1
2
3
4
5
6
<ul>
    <li class="nav1"><a href="#"><img src="img/sprites.gif"/></a></li>
    <li class="nav2"><a href="#"><img src="img/sprites.gif"/></a></li>
    <li class="nav3"><a href="#"><img src="img/sprites.gif"/></a></li>
</ul>
<img src="37.jpg" />

再次測試

007

同樣,結果與預料中一樣,它按照我們想要的順序來執行加載。至於CSS同樣是使用margin負值,並無太多新東西,直接貼出

?
1
2
3
4
5
6
7
8
9
10
11
12
li {float:left; margin:0; padding:0;}
li a {position:relative; float:left; display:inline; height:38px; overflow:hidden;}
li a:hover {margin-left:-1px; padding-left:1px;}
li.nav1 a {width:103px;}
li.nav1 a img {margin:-19px 0 0 0;}
li.nav1 a:hover img {margin:-57px 0 0 0;}
li.nav2 a {width:91px;}
li.nav2 a img {margin:-19px 0 0 -103px;}
li.nav2 a:hover img {margin:-57px 0 0 -103px;}
li.nav3 a {width:106px;}
li.nav3 a img {margin:-19px 0 0 -194px;}
li.nav3 a:hover img {margin:-57px 0 0 -194px;}

效果同樣是正確的,IE6同樣(這裏有一事不解,如果不對li a:hover進行定義,li.nav1 a:hover img的定義在IE6下便會失效)

003

到此,這個效果算是大功告成了。

當然,採用此方法也有弊端,由於按鈕使用的是圖片,在禁用CSS或WAP下瀏覽,瀏覽者就會因爲圖片沒有進行過定位的整張Sprite圖片而感到迷惑。當然,在通常情況下這種方法所帶來的性能優化與所付出的代價相比是值得的。

後記

本文是我認真寫的第一篇博文,並沒有參考過多前輩的文章,所以此方法或許早有人推而廣之了。在這裏還是要感謝淘寶的UED們,你們的作品讓我收穫不已。同時以上分析僅僅是我的一家之言,或許淘寶他們之所以這麼做還有其它理由,或許只有淘寶的UED們纔講得清了。最後,以上分析如有錯誤,請多多指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章