一般一個網頁上面,或多或少都會用到一些小圖標,展示這些小圖標的方法有很多種。最簡單的做法就是將UI圖上面的每個小圖標都保存為圖片,一個小圖標就一張圖片。但這也是比較笨的方法,因為瀏覽器同一時間最多加載的資源是有限的,例如IE7是2個,IE8是6個,chrome是6個,火狐是8個。如果網頁上面有很多張零碎的小圖片,導致請求的次數太多,等待加載狀態中的資源會很多,明顯影響性能。因此,一個改進的辦法是使用sprites圖,將多張小圖放在一張大圖,然后限定展示區域的大小,同時改變圖片的顯示位置background-position來顯示不同的圖標,游戲里面經常使用這種技術,大大減少瀏覽器請求的次數。淘寶網就使用這種技術:
但是要看到這種方法也是有缺點的,即內存和CPU的使用增加,對於移動端低內存和CPU的設備來說,可能會有壓力。使用sprites圖,網上有很多在線的功具可以生成,同時會生成各個小圖標的position位置,例如http://csssprites.com/
第二種改進的辦法是使用base64的編碼方式。將原始二進制的圖片編碼為base64,然后使用css的background: url(data:image/png;base64,%encoding%)的方式,例如百度的首頁搜索欄右邊的話筒就是用這樣的方式:
將圖片進行編碼,可以使用在線工具base64 image,進行轉換。轉換之后,你會發現生成的編碼特別長,其字節數甚至比原始的照片大,大約大33%。以上面的話筒為例,原始照片為1.3kb,而base64的編碼需要1.7kb。同時,另外一個問題是對base64的解析速度比原始二進制的要慢。更嚴重的一個問題是,如果使用太多的base64,會使得css文件太大,下載和解析的時間較長,導致頁面短時間的空白loading狀態,效果可能還不如分開使用一張張圖片。它的優點是不需要借用額外的圖片文件,詳細的分析可以看這篇文章。
第三種方法是使用CSS的技巧,這種方法一般只適用於比較簡單的圖案,例如三角形、五角星、愛心等。例如,如果想要畫一個向上的三角形可以使用下面的方法:
.tri{ width: 0; height: 0; border-left: 50px solid transparent; border-right: 50px solid transparent; border-bottom: 100px solid red; }
它的原理是將一個div的width和height設置成0,那就剩下四個border,四個角都是三角形,令其它三個角不顯示,只留下底部那個角,就是一個向上的三角形。要注意設置左右角的寬度,目的是設置三角形上面兩條邊的長度,再將它們隱藏。更多CSS圖形參考css shape。這種方法看似完美,因為無論是空間占用還是解析速度都比前面兩個方法好,但是這種方式是不自然的,你無法輕易地改變圖形的大小去適應你的頁面,如果你不知道它畫的原理是怎么樣的。第二是無法容易地畫出一些較為復雜的圖案,例如為了畫三個小黃人,花費了2000多行的CSS代碼。另外一個缺點是,它是一個空的span或者div,對於屏幕閱讀者來說是不可見的。
第四種方法是使用icon font,將ui圖里的icon導出制作成一個字體庫,然后跟正常的字體一樣使用,具體制作的方法可參考這篇文章。一般來說,icon font是從svg等矢量格式來的,通過PS導出png的方法可能會存在一些問題。boostrap的glyphicon就是使用icon font。使用時,先用@font-face導入字體(font-face的使用見這篇文章),然后利用一個span,設置font-family為剛剛導入的字體,再通過偽類before或after,屬性content的值為對應圖標的編碼。或者是,直接在html文件里直接插入該圖標的編碼。如下所示:
使用這種方法的優點是很大程序上減少了圖片需要的空間,可以自由改變大小,改變顏色,支持IE6及以上。缺點是只適用於純色的圖標。手機淘寶和百度就使用了這種技術
icon-font的制作方法可參見博主的另外一篇文章:把UI圖里小圖標制成icon font
還有一種辦法是使用Unicode字符,Unicode也提供了很多的圖標和表情,例如打勾,✔ ✓ ☑,使用起來最為簡單,可惜的是,不同的字體差別很大,有些字體沒有這些符號,甚至是同一個字體在不同的設備上看起來也會有差異,例如✔在安卓機上的形狀這是樣的(中間的勾),而在ios上是這樣的
,同樣都是使用了微軟雅黑字體。
上面提及的各種方法都存在一個缺點,沒有語義性,都是一個空的span和div,對屏幕閱讀者不可見。本文介紹一種新的畫小圖標的方法,使用svg結合css3的新屬性clip-path。這種方法的優點是具備語義性,無論在性能還是占用的空間都具有優勢。clip就是裁剪的意思,clip-path原本的用處是用來裁剪圖片,如:
上面,指定裁剪的路徑為一個橢圓,x軸上的半徑為裁剪區域的50%,y軸的半徑為裁剪區域的40%,圓心在(50%, 50%)的位置。在這個橢圓形的封閉區域外的所有元素都不會被瀏覽器渲染出來,使用時要帶有-webkit-前綴和標准的兩種形式。Clippy這個網站可以在線裁剪,當前最新版本的chrome和safari都支持基本形狀的裁剪。除了橢圓外還支持rect(長方形)、cirle(圓形)、inset(帶圓角的長方形)、polygon(多邊形),具體使用可結合上面的博客和網站進行探索。最后一種形式,是使用html里定義的svg元素作為裁剪的目標,這也正是clip-path的生命力所在。因為svg本身提供了豐富的語義定義,可以制作豐富多彩的矢量圖形,更重要的是svg可進行可視化編輯,如AI,inkscape,還有一些在線的編輯器,如svg-editor。關於svg的基本介紹,可參考mdn的教程。
除了裁圖片,利用clip-path的裁剪功能,可以用來制作圖標。原理就是用一個div,設置background顏色和width/height值,然后制作一個圖標的svg路徑,用來裁剪div,就會顯示出相應的小圖標了。以打勾的圖標為例:
首先,制作一個打勾的svg:
<svg width="0" height="0"> <defs> <clipPath id="tick-mask" clipPathUnits="objectBoundingBox"> <path fill="red" stroke="red" stroke-width="1" stroke-miterlimit="10" d="m0.1165671,0.4703638l0.0852069,-0.0852042l0.2337128,0.2335306l0.389592,-0.3894064l0.0852045,0.0852087l-0.4747964,0.4747913z" id="svg_8" clip-rule='evenodd'/> </clipPath> </defs> </svg>
注意這里,不是使用基本形狀,而是使用了svg里的path,貝塞爾曲線,也就是PS/AI里面的鋼筆工具,在d里面定義路徑是如何移動和彎曲的。繪出的形狀要放在clipPath標簽里,給這個clipPath添加一個id,在下面的CSS里將會使用到,同時設置clipPathUnits為objectBoundingBox,作用是將單位設置成比例[0,1],這樣就可以適配出不同大小的形狀。clipPathUnits有兩個取值,另外一個取值是userSpaceOnUse,是默認值,一般單位為px。
形狀畫好了之后,由於要求背景是紅色的,勾是白色的,因此先用一個div,設置紅色背景和圓角,再用一個白底的span裁出一個勾的形狀。如下:
<div class="icon"> <span class='tick'></span> </div>
.icon{ width: 100px; height: 100px; background: #ff7443; text-align: center; background: #ff7443; border-radius: 100px; } .tick{ display: inline-block; -webkit-clip-path: url(#tick-mask); clip-path: url(#tick-mask); /* 在這里對白底的span進行剪切 */ width: 90%; height: 90%; background: white; margin-top: 5%; }
這樣就可以了。這篇文章作者作了一個圓形菜單,還有結合css3的動畫,作了一些很有趣的動態效果。
關於兼容性,IE和edge所有版本不支持clip-path,android的瀏覽器支持url參數的clip-path,但是UC和微信的內置瀏覽器不支持,微博的瀏覽器是支持的,firefox支持帶url參數的。chrome支持-webkit-前綴的,包括基本的形狀和url,safari/ios支持標准形式的,但是safari/ios在渲染上有bug,只要css文件里出現了clip-path,任何元素只要帶position為relative/absolute的都會隱藏掉了,解決辦法是,在這些元素里加多一個css屬性:-webkit-transform: translateZ(0)加大渲染權重,這樣就能顯示出來了。還有可能會出現其它無法渲染的情況,例如,同一個id的clip-path只能渲染出第一個,接下來的都消失了,也可以用這種辦法解決,但是如果渲染過重,在chrome等其它瀏覽器會出現顯示的問題,會顯示錯亂。因此這個問題比較麻煩,h5開發的時候需要注意。
對於無法支持的瀏覽器,改用其它的辦法,得做個區分。可以借鑒modernizr提供的辦法,頁面加載時,首先創建一個svg和一個div,設置這個div的clip-path CSS屬性,然后調用getComputedStyle看是否仍有剛剛設置的屬性,如果有說明支持,沒有說明不支持。如果支持就給body添加一個has-clip-path的類,不支持就為no-clip-path,然后在需要使用圖標的元素的css前面加多一個clip-path的類,有和沒有兩個。這樣就達到了區分的目的,不支持的就使用其它的方式。
<body> <svg style="display:none" width="0" height="0"><defs><clipPath id="_svg"><path d="M 0 0 L 0 0"></path></clipPath></defs></svg> <div style="-webkit-clip-path:url(#_svg);clip-path:(#_svg);display:none" id="_test"></div> <script> var style = document.defaultView.getComputedStyle(document.getElementById("_test"), null); var body = document.getElementsByTagName("body")[0]; if(style.WebkitClipPath !== "url(#_svg)" && style.clipPath !== "url(#_svg") body.className = "no-clip-path"; else body.className = "has-clip-path"; </script>
<!--body的其它元素--> </body>
本來可以使用svg和clip-path做為h5開發,但是考慮到安卓上的某些國內瀏覽器不支持,以及safari讓人頭疼的渲染問題,所以就目前的情況來說應用到生產環境仍不太樂觀。所以在PC的web端使用sprites圖,在移動的h5端使用icon font並靈活結合其它方法。
注意到,icon-font和clip-path本質都是一樣的,都是使用了svg,只是使用的方式不同。因此在提供icon font圖標的網站上,如icomoon和fontello上,可將圖標的svg制作字體,也可作為clip-path使用。
參考:
1. CSS vs. SVG: Shapes and Arbitrarily-Shaped UI Components 這篇文章比較了使用CSS和svg畫圖標的兩種方法,強調了使用svg畫圖的優點。
2. SVG Tutorial,MDN一個關於svg的簡明易懂的入門教程。
3. icomoon和fontello,提供icon-font/svg小圖標的網站。
4. Clippy在線操作clip-path