《CSS Sprites: Image Slicing’s Kiss of Death》是一篇舊文,于2004年3月5日發(fā)表于alistapart的第173期,作者是大名鼎鼎的CSS禪意花園的園主Dave Shea。之所以把它翻出來(lái),是因為最近在看的關(guān)于頁(yè)面優(yōu)化的一些文章,都不約而同地提到了這項技術(shù),溫故知新~
有趣的是,關(guān)于CSS Sprites這個(gè)名稱(chēng)的中文翻譯一直沒(méi)有定論,如果依照詞典直譯成樣式表精靈,恐怕沒(méi)有幾個(gè)人會(huì )同意這樣的叫法。就好像有人把其后的image slicing直譯成圖像限制一樣,會(huì )讓人摸不著(zhù)頭腦~
和直譯不同,也有人把它意譯為CSS圖像拼合,的確,整個(gè)css sprites的圖像制作過(guò)程就是一種圖像拼合,將頁(yè)面所需的修飾性圖像全部打包在一張圖像中。但是對于如何用css層面上的positio定位來(lái)使用這個(gè)拼合的圖像,這個(gè)名稱(chēng)就顯得名不副實(shí)了。我倒是認為,這種css控制背景圖像位置的技術(shù)和3D軟件中的UVW貼圖有神似的地方,暫且就叫它樣式表貼圖定位吧。說(shuō)到底,叫什么不重要,了解它才是王道……
---------------------------------------- 開(kāi)始正文的分割線(xiàn) ----------------------------------------
原文如下:
回到電視游戲盛行的年代(我們這里講的是8位游戲機的輝煌時(shí)期),那時(shí)的圖像非常的簡(jiǎn)單,位圖制成的二維形象和背景場(chǎng)景獨立分開(kāi),這一點(diǎn)很像現在的像素藝術(shù)。成百上千的小圖像成為構建整個(gè)游戲的視覺(jué)基石。

當游戲慢慢變得復雜,技術(shù)的提升使得管理眾多的小圖片成為可能,游戲畫(huà)面變得流暢自然起來(lái)。其中一點(diǎn)就是,貼圖定位技術(shù)的引入,在代碼控制下抽出所需的圖片,這些圖片在主圖層中被映射于各自位置的之上,并有選擇性地將其呈現于屏幕上。
這和Web有什么關(guān)系?
三十年河東三十年河西,雖然3D游戲的興起讓貼圖技術(shù)日落西山,但同時(shí)興起的2D手機游戲讓貼圖技術(shù)有了煥發(fā)新生的可能?,F在,通過(guò)一點(diǎn)點(diǎn)的數學(xué)計算以及完備的css技術(shù),我們將這個(gè)基本概念應用于整個(gè)web設計之中。
確切地說(shuō),我們將用CSS方案取代陳舊的圖像切片(加上JavaScript)技術(shù)。更進(jìn)一步的闡述是:建立一種網(wǎng)格圖像,采用一個(gè)方法將網(wǎng)格中的每一個(gè)獨立小單元抽出。我們可以在這個(gè)主圖層文件中放置按鈕、導航以及一切元素,連同它們“初始”和“之后”的鏈接狀態(tài)。
樣式表貼圖定位如何運作?
在css應用上我們只需要一點(diǎn)創(chuàng )造性思維,這就是全部了。
讓我們從主圖層開(kāi)始。將其分成四等分矩形,在主圖層圖像中你將看到“初始”圖像在上,“之后”的懸停狀態(tài)在其下?,F在4個(gè)鏈接之間并沒(méi)有清晰的界線(xiàn),現在想象一下每一塊文字對應一個(gè)鏈接。(為了簡(jiǎn)單起見(jiàn),在其后的文章中,我們依然把鏈接圖像叫做“初始”圖像,懸停狀態(tài)下叫“之后”。對諸如:active,:focus以及:visited的鏈接狀態(tài),這里不展開(kāi)討論。)
熟悉Petr Stanicek(Pixy)快速翻轉圖像方法的朋友也許已經(jīng)猜出我們將要做什么。感謝Pixy的這個(gè)樣例,我們接下來(lái)的討論都是基于它的基礎功能。但是別高興的太早了~
先來(lái)看HTML部分。每一個(gè)優(yōu)秀的css技巧都力圖在最簡(jiǎn)潔的代碼塊上添加視覺(jué)效果。本方法也不例外。
1: <ul id="skyline">
2: <li id="panel1b"><a href="#1"></a></li>
3: <li id="panel2b"><a href="#2"></a></li>
4: <li id="panel3b"><a href="#3"></a></li>
5: <li id="panel4b"><a href="#4"></a></li>
6: </ul>
以上的代碼將是我們這個(gè)例子的基礎。簡(jiǎn)單的標記將使得代碼在老舊的、對css支持差的瀏覽器中也得以運行,這也是行業(yè)中的一個(gè)良好趨勢。對于我們力爭要達到的目的,這是個(gè)很不錯的主意。(我們在這里忽略了鏈接中的文字,你可以使用你所鐘愛(ài)的圖像替代法來(lái)隱藏最后添加的文字。)
CSS的運用
有了這些編寫(xiě)好的塊元素,我們可以開(kāi)始著(zhù)手css。在開(kāi)始之前有一點(diǎn)需要注意——由于IE的一個(gè)bug,我們需要讓“之后”圖像緊貼在“初始”圖像之后,而不是一張取代另一張的做法。盡管當我們把它們排成一行時(shí),并沒(méi)有什么真正的視覺(jué)差別,但這樣會(huì )避免一個(gè)我們不想見(jiàn)到的加載“閃爍(flicker)”。
1: #skyline { 2: width: 400px; height: 200px; 3: background: url(test-3.jpg); 4: margin: 10px auto; padding: 0; 5: position: relative;} 6: #skyline li { 7: margin: 0; padding: 0; list-style: none; 8: position: absolute; top: 0;} 9: #skyline li, #skyline a { 10: height: 200px; display: block;}和想象中不同,我們沒(méi)有把“初始”圖像指定給這些鏈接,而是用<ul>代替,后面你將看到這是為什么。
css的其他部分,分別給#skyline和列表項定義了尺寸、范圍,給列表項定位,以及去除了不想要看到的默認列表樣式中的圓點(diǎn)。
我們將鏈接作為一個(gè)透明的空層,通過(guò)父層級<li>來(lái)給它們定位。如果我們跳過(guò)<li>,直接定位這些鏈接元素,在老的瀏覽器上將會(huì )出現錯誤,所以我們不能這么做。
定位鏈接
<li>元素被絕對定位,為什么它們沒(méi)有出現在瀏覽器窗口的頂端?那是因為對于絕對定位元素有一個(gè)奇異(quirky),但卻很使用的特性(roc:我不覺(jué)得有什么奇異的地方),絕對定位元素會(huì )以離它們最近的父級定位元素為基點(diǎn),而不是瀏覽器窗口這個(gè)根元素。因為我們給#skyline定義了position: relative;屬性,所以我們可以將<li>元素絕對定位于#skyline的左上角。
1: #panel1b {left: 0; width: 95px;} 2: #panel2b {left: 96px; width: 75px;} 3: #panel3b {left: 172px; width: 110px;} 4: #panel4b {left: 283px; width: 117px;}因此#panel1不再獨占水平位置,#panel2b基于#skyline水平向左96px,其他的也類(lèi)似。我們將鏈接定義為display: block;使其高度和<li>相同,這樣我們就可以填充這些<li>了。這正是我們想要得到的效果。
現在我們有了一個(gè)鏈接效果的基礎圖片,但懸停狀態(tài)還沒(méi)有設置。查看樣例,加上邊框也許可以看得更清楚些。
懸停
在過(guò)去,為懸停狀態(tài)翻轉一張新圖片我們需要使用一些JavaScript?,F在我們將所有的集中在一張圖片上,我們需要一種有選擇的抽出各種狀態(tài)的方法。
如果我們在:hover狀態(tài)下應用整張貼圖,不附加其他值,我們只能看到左上角——這不是我們想要得到的,雖然我們的確希望依照鏈接的范圍裁剪。我們需要以某種方式移動(dòng)這個(gè)貼圖的位置。
1: #panel1b a:hover { 2: background: transparent url(test-3.jpg) 3: 0 -200px no-repeat;} 4: #panel2b a:hover { 5: background: transparent url(test-3.jpg) 6: -96px -200px no-repeat;} 7: #panel3b a:hover { 8: background: transparent url(test-3.jpg) 9: -172px -200px no-repeat;} 10: #panel4b a:hover { 11: background: transparent url(test-3.jpg) 12: -283px -200px no-repeat;}這些像素位置值要如何放置?我來(lái)解釋一下:第一個(gè)值用來(lái)設置水平位置,第二個(gè)用來(lái)設置垂直位置。
每一個(gè)垂直位置都是相同的;由于整張貼圖的高度是400px,初始和懸停各占據上下一半,所以我們將很容易的計算出高度值。想要將整個(gè)背景貼圖向上移動(dòng)200px,我們需要給一個(gè)數量相同的負值。以這個(gè)鏈接的頂端或者說(shuō)0高度作為原點(diǎn),背景圖將在這個(gè)原點(diǎn)200px之上,也就相當于說(shuō),我們將原點(diǎn)設在了-200px上。
同樣的,如果每一個(gè)鏈接的左邊界都是0,我們需依據<li>的寬度,給背景貼圖一個(gè)偏移值,就像前面我們做過(guò)的那樣。所以,第一個(gè)鏈接不需要偏移,因為它就是水平位置的原點(diǎn)。第二個(gè)鏈接需要的偏移值是第一個(gè)鏈接的寬度,第三個(gè)鏈接的偏移值是前兩個(gè)寬度相加,而最后一個(gè)鏈接的偏移值則是之前三個(gè)寬度的綜合。
這個(gè)過(guò)程解析起來(lái)有點(diǎn)繞,不是嗎?但如果親手去調試一下,你將很快理解它們的工作原理,一旦你對此熟悉起來(lái),它們看上去就不那么難搞了。
看看我們得到了什么,一個(gè)單一圖片翻轉轉化成為了一個(gè)無(wú)序列表。
按鈕
在上面的例子里,并不是出于特別的原因,我們將鏈接彼此相鄰。圖像地圖也許在有些地方比較合適,但把它們分開(kāi)作為各自獨立的按鈕又會(huì )如何?我們可以給其添加邊框和邊距,
實(shí)際上,積木已經(jīng)搭好了。我們其實(shí)不比對現有的代碼大動(dòng)干戈;最大的變動(dòng)就是需要創(chuàng )建一個(gè)新的背景圖片,而不是像上個(gè)例子中讓鏈接彼此相連。由于我們不能給<ul>應用一個(gè)整體的原始背景圖,我們以<li>代替,并且像上面討論過(guò)的那樣設置懸停狀態(tài)下的偏移值。
使用一張合適的圖片以及給<li>之間添加些許邊距,我們就得到了這些按鈕。
在這個(gè)例子里,我們添加了1px的邊框,鏈接的最終寬度增加,這會(huì )影響到偏移值;我們需要增加2px的偏移值以抵消這一影響。
不規則形狀
到目前為止我們都把精力放在不重疊的矩形形狀上。如何將大量的復雜圖像切片處理的就像是用Fireworks和ImageReady輸出的那樣簡(jiǎn)單呢?別緊張,我們同樣可以做到。
開(kāi)始的部分,我們采用第一個(gè)例子中同樣的方法。給<ul>設置背景圖,然后取消列表項的項目符號,以及寬度等其他定義。顯著(zhù)的區別在于我們要給這些<li>定位;目的就是讓這張圖片上每一個(gè)圖形被包裹住。
因為我們相對于<ul>的左上角應用絕對定位,我們得以隨心所欲地將鏈接精確放置?,F在只剩下要處理懸停狀態(tài)了。
值得注意的是,在這個(gè)案例中,僅僅設置初始和懸停狀態(tài)圖片是不夠的。因為它們彼此重疊,所以在只是設置了一個(gè)懸停狀態(tài)之后,塊和塊之間在懸停下會(huì )彼此露餡。事實(shí)上,它顯示的塊就是這個(gè)鏈接的邊框范圍。(這里可以看得很清楚)
那么如何避免呢?解決方法是添加第二種懸停狀態(tài)圖片背景,并且仔細的選擇哪些圖形需要出現。重新制作的貼圖背景被分割成在第一個(gè)懸停狀態(tài)下紫色和藍色圖形出現,綠色、橙色和黃色圖形在第二個(gè)懸停狀態(tài)下出現。這樣就避免了每一個(gè)圖形在懸停狀態(tài)下露出旁邊的圖形。大功告成。
優(yōu)缺點(diǎn)
最后的幾點(diǎn)關(guān)鍵。我們新的樣式表貼圖定位法在絕大部分的現代瀏覽器運作的很好。(Roc:這里的觀(guān)點(diǎn)都是對的,但文本誕生的時(shí)間和現在——相對于瀏覽器升級速度——相去甚遠,Opera6和7的版本就像是石器時(shí)代那么遙遠了,所以這一段可以跳過(guò))Opera 6是個(gè)例外,在懸停狀態(tài)下,它無(wú)法應用背景圖片。為什么?我們還不能確定,但它意味著(zhù)我們的懸停不起作用了。鏈接依然有效,如果它們都標記正確的話(huà),最終的結果就是在Opera 6下它呈現出一個(gè)靜態(tài)的,但可用的圖片地圖。我們寄希望于即將推出的Opera 7。
另一個(gè)困擾是FIR。在極少數情況下,用戶(hù)禁止了下載圖片,但依然保持CSS樣式,那么在頁(yè)面上原來(lái)由圖片占據的地方就會(huì )出現一片空白。鏈接雖然仍在,但卻是不可見(jiàn)的。截至目前,還沒(méi)有很好的解決之道。
還有就是文件的大小。有一種觀(guān)點(diǎn)就是認為雙倍尺寸的圖片文件一定大過(guò)這些單一切片的集合,因為圖片涵蓋的尺寸比較大。所有的圖片格式都有一個(gè)極限值(例如1x1px的gif圖片總是在50bytes左右),切片越多,這種極限值就越快的堆積。此外,一張大的gif貼圖只需要一個(gè)顏色表(color table),如果是切片,每一個(gè)切片圖都需要附加一個(gè)自己的顏色表。
最后,不要忘了我們的標記是干凈漂亮的,這是其優(yōu)勢所在。HTML列表完美,并且圖片替代文本鏈接技術(shù)對于屏幕瀏覽者可用性更高。替換貼圖里圖形更是簡(jiǎn)單無(wú)比,因為我們只用一個(gè)css文件就可以控制尺寸大小和定位,所有的圖形也都在一張大圖之中。
---------------------------------------- 翻譯結束的分割線(xiàn) ----------------------------------------
感謝耐心看完我拙劣的翻譯。
在css這個(gè)領(lǐng)域,04年的文章完全可以稱(chēng)得上是古董級,但這篇依然沒(méi)有失去它的價(jià)值,盡管現在看來(lái)CSS Sprites在技術(shù)并沒(méi)有太多的超前性,但是完美應用這項技術(shù)的網(wǎng)站并沒(méi)有許多。因為,CSS Sprites這項css技術(shù)的優(yōu)勢根本在于減少了服務(wù)器請求次數,壓縮了帶寬開(kāi)支,越是大型的網(wǎng)站越是看重這一點(diǎn)。
另外有一點(diǎn)對于使用Dreamweaver CS4 Beta的朋友需要注意,這個(gè)測試版本很顯然它的可視化引擎不能正確處理背景位置,在可視化下編輯CSS Sprites,你會(huì )看到背景圖片到處亂飛。CS3的正式版中沒(méi)有這個(gè)問(wèn)題。
最后,有一個(gè)好消息,Dave Shea時(shí)隔4年多推出了他的又一篇力作:CSS Sprites2 - It's JavaScript Time
關(guān)注樣式表貼圖定位技術(shù)的朋友一定不能錯過(guò)。

