當前位置:
首頁 > 科技 > 關於雪碧圖預處理和後處理方案的討論

關於雪碧圖預處理和後處理方案的討論

前言

本文的來源前天【第1068期】web項目中圖標的前端處理方案這篇推送完,也是在朋友圈跟本文作者的討論,然後產生的作者對雪碧圖的經驗分享。今日早讀文章由@HaoyCn 分享。

正文從這開始~

前端小圖標處理方案眾多,本文主要介紹基於雪碧圖的處理方案,分析雪碧圖的預處理和後處理模式的得與失,以及在項目中通常會遇到的問題以及解決方案。其他小圖標處理方案未在此文探討之列。

討論的起點是工程工具 Gulp/Webpack 上的集成方案(手動拼合雪碧圖的做法已經作古了)。

1. 預處理方案

代表:gulp.spritesmith 和 webpack-spritesmith

預處理方案是預先指定需要生成的雪碧圖切片元素,由工具合成後,得到相應的雪碧圖和數據 (S)CSS 文件,開發中將二者投入使用。

數據文件內容通常是可定義的,我們可以自定義模板或者內容生成函數(怎麼寫這裡就不探討了)。藉助 SCSS 等 CSS 預編譯語言的威力,如何使用雪碧圖數據就極具便利性。

注意,下面的 SCSS 代碼都是寫的模板函數生成的內容,模板函數具體怎麼寫不在本文探討範圍內。

最簡單的,我們可以直接生成類(如),如:

.icon-home{

width:12px;

height:12px;

background:url(sprite.png)-56px -48px no-repeat;

}

這樣在 HTML 結構中可以直接使用。但如果切片元素眾多,每一條規則都要有單獨的 background 屬性的話,未免太冗長,稍微改進下,把公共的 background-image 屬性提取出來(高清模式下還有 background-size,以下從簡討論),可以生成這樣的內容:

.icon-home{

width:12px;

height:12px;

background-position:-56px -48px;

}

.icon-back{

width:18px;

height:20px;

background-position:-10px 0;

}

.icon-home,

.icon-back{

background-image:url(sprite.png);

}

但是,如果有的類沒用到,白白生成一個類豈不是很沒必要?或者我們不想使用 .icon-home 這樣子的類名,那我們用 SCSS 佔位符可以解決這個問題:

%-icon-home{

width:12px;

height:12px;

background-position:-56px -48px;

}

%-icon-home,

%-icon-back{

background-image:url(sprite.png);

background-repeat:no-repeat;

}

然後需要用到的時候,再繼承這個佔位符:

.head-home{

@extend%-icon-home;

}

這樣,我們就能用上自定義的類名,並節省 CSS。

但如果遇到狀態性變化怎麼辦?比如 :hover 時小圖標變紅,這時候其實除了 background-position 改變外,其他數據是沒有變化的。

按上述方案,我們的寫法會是:

.head-home{

@extend%-icon-home;

&:hover{

@extend%-icon-home_hover;

}

}

生成的最終 CSS 是:

.head-home{

width:12px;

height:12px;

background-position:-56px -48px;

}

.head-home:hover{

width:12px;

height:12px;

background-position:0 0;

}

.head-home,

.head-home:hover{

background-image:url(sprite.png);

}

思索發現,本質上佔位符只是包含了圖片信息,那我們換一種更加高端點的寫法:

// 把雪碧圖數據保存為一個 SCSS Map 數據

$__sprite__:(

home :(

width :12,

height :12,

x :56,

y :48,

url : sprite.png

),

home_hover :(

width :12,

height :12,

x :0,

y :0,

url : sprite.png

)

);

// 公共部分依然佔位符

%-sprite-common{

background-image:url(sprite.png);

}

// 輸出 background-position

@mixinsprite-position($name){

$data:map-get($__sprite__, $name);

$x:map-get($data, x );

$y:map-get($data, y );

background-position: -#{$x}px -#{$y}px;

}

// 輸出其他切片元素私有數據

@mixinsprite-item($name){

// 繼承公共樣式

@extend%-sprite-common;

// 設置獨有樣式

@includesprite-position($name);

$data:map-get($__sprite__, $name);

$width:map-get($data, width );

width:unquote($width + px );

height:unquote(map-get($data, height )+ px );

}

生成出以上 SCSS 模板後,我們就可以這樣使用雪碧圖了:

.head-home{

@includesprite-item( home );

&:hover{

@includesprite-position( home_hover );

}

}

生成出來的效果類似於:

.head-home{

background-image:url(sprite.png);

}

.head-home{

background-position:-56px -48px;

width:12px;

height:12px;

}

.head-home:hover{

background-position:-0px -0px;

}

這樣,在 :hover 態下生成的 CSS 規則只會包含必要的變動。

總結起來,預處理模式的優點在於,通過自定義 CSS 數據文件,可以隨心所欲地使用已經生成出來的雪碧圖信息。

然而,在預處理模式下,開發的頁面依賴的是生成後的雪碧圖,而不是合併前的雪碧圖切片元素,隨之帶來的問題是:沒辦法實現雪碧圖的按需合併。

預處理方案一般以頁面為單元組織雪碧圖。思考這樣的問題:如果一張雪碧圖對應一個頁面,那各頁面的公共組件使用的雪碧圖,是每個頁面各一份副本,還是只保存一次切片元素,把通用的抽成一張公共雪碧圖呢?

如果各存一份,公共組件的切片元素就得保存到多個文件夾,每次更新、刪除、添加的時候得同步多處。如果管理不當,就會導致頁面元素不同、廢棄的切片仍被合併以及遺漏等問題。(以 CSS 文件為單元組織雪碧也會遇到類似的情況。)

後處理方案則解決了這些問題。

2. 後處理方案

典型:postcss-sprites

後處理方案通過對已經生成的 CSS 文件進行分析,將其中包含切片元素的 background 或 background-image 作為依賴收集,合併成雪碧圖後再將相關參數替換。

如上述典型工具,生成前是:

.comment{

background:url(images/sprite/ico-comment.png)no-repeat 0 0;

}

.bubble{

background:url(images/sprite/ico-bubble.png)no-repeat 0 0;

}

生成後是:

.comment{

background-image:url(images/sprite.png);

background-position:0 0;

}

.bubble{

background-image:url(images/sprite.png);

background-position:0 -50px;

}

如此一來,CSS 中有哪些切片元素就合併哪些,不會把沒有用到的切片也合併進去。即一張 CSS 樣式表有一張專門的雪碧圖。

不過,正如上面所看到的,後處理模式解決了按需合併的問題,也不會造成頁面/組件間雪碧圖的耦合,但卻喪失了預處理方案中直接利用數據的便捷性。在預處理方案中,我們不用人工地去衡量切片元素的寬高,而是讓 SCSS 自動輸出,後處理方案卻做不到這一點。

3. 預處理和後處理相結合

既要能像預處理那樣不用人工的地去量切片,又要像後處理那樣實現按需的合併,這就是我理想的開發模式。

基於以上探索,我寫了個工具:postcss-sprite-property 來實現二者的平衡。做法是:

區分開發環境和生產環境。在開發環境中,不合併雪碧圖,直接使用切片元素預覽;生產環境中,為每一張樣式表內的切片合併雪碧圖

利用 node-sass 所支持的自定義函數功能,為 SCSS 注入 image-width 和 image-height 兩個自定義函數,用來查詢圖片寬高數據

使用 @sprite-item 和 @sprite-position 兩個混合,優雅地定義一張雪碧圖元素

盡量為公共屬性生成一個公共的規則

Webpack 中,開發時不拼合雪碧圖,讓切片本身作為組件依賴;打包時合併雪碧圖並剔除切片元素

來看一個實例:

我們把如下樣式放進我們的公共樣式庫中:

// 使用雪碧圖

// 以圖片名作為參數

@mixinsprite-item($name){

// 統一一個路徑存放切片元素

// 這樣就能得到圖片的實際地址了

$url: ../asset/sprite/#{$name}.png ;

// 注入的 `image-width` 函數可以幫我們查詢圖片寬度

$width:image-width($url);

@if(null == $width){

@warn Sprite element `#{$name}` not found! ;

}@else{

// 獲取圖片高度

$height:image-height($url);

// 自動書寫 `width` 和 `height`

width:$width;

height:$height;

// 定義背景

// 開發模式下直接按輸出預覽

// 生產環境下將其替換為雪碧圖的數據

background:url($url)0 0 / #{$width}#{$height}no-repeat;

}

}

// 改變雪碧圖 `background-position`

// 和上面的 `sprite-item` 區別僅在於

// 這個混合不輸出 `width` 和 `height`

@mixinsprite-position($name){

$url: ../asset/sprite/#{$name}.png ;

$width:image-width($url);

@if(null == $width){

@warn Sprite element `#{$name}` not found! ;

}@else{

$height:image-height($url);

background:url($url)0 0 / #{$width}#{$height}no-repeat;

}

}

現在,具體項目里就能這樣使用了:

.home-head{

@includesprite-item( pageA/home );

&:hover{

@includesprite-position( pageA/home_hover );

}

}

.home-back{

@includesprite-item( pageB/back );

}

開發階段,沒有合成雪碧圖,直接使用切片預覽,所以效果是這樣的:

.home-head{

width:12px;

height:12px;

background:url("../asset/sprite/pageA/home.png")0 0 / 12px 12px no-repeat;

}

.home-head:hover{

background:url("../asset/sprite/pageA/home_hover.png")0 0 / 12px 12px no-repeat;

}

.home-back{

width:18px;

height:20px;

background:url("../asset/sprite/pageB/back.png")0 0 / 18px 20px no-repeat;

}

最後發布生成的樣式則是這樣的:

.home-head{

width:12px;

height:12px;

background-position:-56px -48px;

}

.home-head:hover{

background-position:0 0;

}

.home-back{

width:18px;

height:20px;

background-position:-10px 0;

}

.home-head,

.home-head:hover,

.home-back{

background-image:url(sprite.png);

}

當然,如上所示,.home-head 和 .home-head:hover 都在最後的公共規則中。最理想的當然是沒有更好,但作為一個平衡方案,這仍是可接受的。

更具體的用法和更多的功能請參看 Github:https://github.com/HaoyCn/pos...

4. REM 布局中雪碧圖的錯位問題

這個算是雪碧圖的硬傷。其他小圖標方案不會有這個問題。REM 布局裡雪碧圖錯位幾乎是不可避免的,在安卓下錯位甚至可以達到 2px 左右。我也總結出一個 CSS 兜錯方案:

background-position 使用百分比單位

給雪碧圖增加透明內邊距,如 spritesmith 工具設置 padding: 8

增加 1-2px 的容錯區域,概要代碼如下:

%-sprite{

// 錯位時的容錯區域

padding:1px;

// 從內容區開始繪製背景

background-origin:content-box;

// 在內邊距盒外裁切背景

background-clip:padding-box;

}

利用以上三個屬性的組合來騰出容錯的空間。

4. 結語

關於雪碧圖的處理方案的討論就到此結束了。待 HTTP2 普及之後,也就沒有雪碧圖什麼事了,而現在仍有其存在的必要性。

處理小圖標還有其他的方案,如 Iconfont 和 Svg-Sprite。總之沒有最好的,只有最適合的。

關於本文

作者:@HaoyCn


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端早讀課 的精彩文章:

重新授權許可React、Jest、Flow與Immutable.js
CSS選擇器從右向左的匹配規則
web項目中圖標的前端處理方案
你知道「編譯」與「解釋」的區別嗎?
剖析 iOS 11 網頁適配問題

TAG:前端早讀課 |