web圖標(biāo)的工程化方案
點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號(hào)
回復(fù)算法,加入前端編程面試算法每日一題群

來源:周周醬愛學(xué)習(xí)
https://juejin.cn/post/6952150039732944910
如何管理圖標(biāo)是我們?cè)趙eb項(xiàng)目開發(fā)過程中都會(huì)遇到的問題。隨著web技術(shù)的發(fā)展,圖標(biāo)方案也經(jīng)歷了幾個(gè)階段,以下這幾種圖標(biāo)方案也基本能涵蓋web圖標(biāo)使用的歷程。
image sprite
最早通過img標(biāo)簽或background-image來引用圖標(biāo),每個(gè)圖標(biāo)單獨(dú)引用,后來為了減少http的請(qǐng)求,提高網(wǎng)站性能,提出sprite的概念,將小的 png 圖片合并到一張圖上,然后根據(jù) background-position 來顯示不同的圖片。
優(yōu)勢(shì):
兼容性好 還原度高
劣勢(shì):
同一圖標(biāo)的不同顏色需要設(shè)計(jì)多個(gè)圖片 新增圖標(biāo)需要重新合成sprite 由于是位圖,兼容不同分辨率需要多套尺寸
iconfont
為了更易于控制圖標(biāo)顏色和大小,并兼容各種設(shè)備屏幕,后來開始出現(xiàn)iconfont方案。iconfont就是圖標(biāo)字體,我們可以像使用普通字體一樣使用它,只要適合字體相關(guān)的CSS屬性都適合字體圖標(biāo),使用font-size和color就可以輕松控制圖標(biāo)的大小和顏色。隨著各種字體圖標(biāo)庫(kù)網(wǎng)站的出現(xiàn),國(guó)外的icomoon.io,或者國(guó)內(nèi)阿里的iconfont.cn,帶來的全套解決方案,使iconfont流行起來。
優(yōu)勢(shì):
能夠容易地改變圖標(biāo)的顏色,尺寸 矢量圖不失真 兼容所有流行的瀏覽器,在h5和app上都能使用 替換圖標(biāo)和新增圖標(biāo)也非常簡(jiǎn)單,也不需要考慮圖標(biāo)合并的問題
劣勢(shì):
只支持單色圖標(biāo) 字體渲染,低倍屏下容易出現(xiàn)鋸齒
引用方式
iconfont的使用方式也很簡(jiǎn)單,使用 @font-face 引入字體格式,就和使用其他自定義字體一樣。
@spicons-font-path: "./fonts";
@font-face {
font-family: "spicons";
src: url('@{spicons-font-path}/spicons.eot?v=1');
src: url('@{spicons-font-path}/spicons.eot?v=1#iefix') format('eot'),
url('@{spicons-font-path}/spicons.ttf?v=1') format('truetype'),
url('@{spicons-font-path}/spicons.woff?v=1') format('woff'),
url('@{spicons-font-path}/spicons.svg?v=1#spicons') format('svg');
}
.sp-icon {
font-family: "spicons";
display: inline-block;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
text-rendering: auto;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
復(fù)制代碼
unicode 引用
<div class="sp-icon"></div>
復(fù)制代碼
使用配套生成的樣式
<link rel="stylesheet" href="./iconfont.css">
<span class="sp-icon icon-xxx"></span>
復(fù)制代碼
工程化方式
依賴網(wǎng)站生成字體文件,在替換或加圖標(biāo)后需要重新覆蓋項(xiàng)目中的圖標(biāo)css代碼,不夠自動(dòng)化。可以使用工具腳本在本地項(xiàng)目中將SVG文件生成iconfont,更方便地管理項(xiàng)目圖標(biāo)。需要在項(xiàng)目中安裝以下依賴庫(kù)。
gulp (自動(dòng)化構(gòu)建工具) gulp-iconfont (使用gulp將svg圖標(biāo)集合創(chuàng)建為 svg/ttf/eot/woff/woff2字體) gulp-iconfont-css (結(jié)合gulp-iconfont生成配套的css樣式)
gulpfile
var gulp = require('gulp');
var iconfont = require('gulp-iconfont');
var iconfontCss = require('gulp-iconfont-css');
/** 生成圖標(biāo)字體文件*/
gulp.task('Iconfont', function() {
return gulp
.src(['../src/icons/svg/*.svg'])
.pipe(
iconfontCss({
fontName: 'spicons', //字體名
path: '../src/icons/templates/iconFont.less', //模板文件路徑
targetPath: '../spicons-icons.less',//樣式文件目標(biāo)地址相對(duì)于dest目錄
cssClass: 'sp-icon' //樣式類名
})
)
.pipe(
iconfont({
fontName: 'spicons', // required
formats: ['ttf', 'eot', 'woff', 'svg']
})
)
.pipe(gulp.dest('../src/styles/iconfont/fonts'));
});
gulp.task('default',gulp.series('Iconfont'));
復(fù)制代碼
gulp-iconfont-css支持使用自定義的css模板,來組織生成的字體配套樣式,以靈活的方式創(chuàng)建符合項(xiàng)目規(guī)范的樣式命名,引用路徑。
less模板
@spicons-font-path: "./fonts";
@font-face {
font-family: "<%= fontName %>";
src: url('@{spicons-font-path}/<%= fontName %>.eot?v=1');
src: url('@{spicons-font-path}/<%= fontName %>.eot?v=1#iefix') format('eot'),
url('@{spicons-font-path}/<%= fontName %>.ttf?v=1') format('truetype'),
url('@{spicons-font-path}/<%= fontName %>.woff?v=1') format('woff'),
url('@{spicons-font-path}/<%= fontName %>.svg?v=1#<%= fontName %>') format('svg');
}
.<%= cssClass%> {
font-family: "<%= fontName %>";
display: inline-block;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
text-rendering: auto;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
<% _.each(glyphs, function(glyph) { %>@icon-<%= glyph.fileName.replace(/[\d]{1,}([\-])/, "") %>: "\<%= glyph.codePoint %>";
<% }); %>
.<%= cssClass%>-char(@icon-variable) {
content: @@icon-variable;
}
.<%= cssClass%>-base-pseudo(@filename, @insert: before) {
@pseudo-selector: ~":@{insert}";
@icon-variable: ~"icon-@{filename}";
&@{pseudo-selector} {
.<%= cssClass%>-char(@icon-variable);
}
}
<% _.each(glyphs, function(glyph) {
%>.<%= cssClass%>-<%= glyph.fileName.replace(/[\d]{1,}([\-])/, "") %> {
.<%= cssClass%>-base-pseudo(<%= glyph.fileName.replace(/[\d]{1,}([\-])/, "") %>);
}
<% }); %>
復(fù)制代碼
執(zhí)行構(gòu)建圖標(biāo)命令
gulp \--gulpfile build/build-icon.js生成字體圖標(biāo)以及樣式文件

樣式文件

svg sprite
svg意為可縮放矢量圖形,它不會(huì)像位圖一樣因?yàn)榉糯蠖д妫诓煌直媛氏碌谋憩F(xiàn)都一樣清晰。svg其實(shí)很早就出現(xiàn)了,只不過由于兼容性的問題,早前并沒有很好地發(fā)揮出它的價(jià)值,但隨著IE6/7/8退出舞臺(tái), android 4.x 的開始,很多網(wǎng)站都開始使用svg作為圖標(biāo)方案 。github在16年的時(shí)候已經(jīng)使用svg替代iconfont,ant design在 3.9.0 之后,使用了 svg圖標(biāo)替換了原先的 font 圖標(biāo)。
優(yōu)勢(shì):
能夠容易地改變圖標(biāo)的顏色,尺寸 支持彩色圖標(biāo) 矢量圖不失真 可讀性好,有利于SEO與無障礙 渲染效果好,不會(huì)有鋸齒
劣勢(shì):
體積相對(duì)于iconfont較大
引入方式
使用img、object、embed標(biāo)簽或者作為background背景圖直接引用svg,或者可以合并成雪碧圖,這種方式與png雪碧圖使用方法一樣。inline svg,svg本身是一個(gè)html標(biāo)簽,可以直接把svg寫入 html中。
<body>
<div>
<svg>....</svg>
</div>
</body>
復(fù)制代碼
使用 svg中的 symbol,use 元素來制作圖標(biāo)
標(biāo)記的作用是定義一個(gè)圖像模板,本身不會(huì)輸出任何圖像,可以使用標(biāo)記實(shí)例化它。在實(shí)際項(xiàng)目中我們會(huì)有很多圖標(biāo),將零散的svg合并,每個(gè)圖標(biāo)有唯一的symbol,通過symbol來引用。將symbol定義插入到頁(yè)面body中,然后在需要使用的地方通過引用。將xlink:href設(shè)置為定義的symbol id即可。
<svg style="display: none;">
<symbol viewBox="0 0 16 16" id="icon-bell">
<path fill="#ffa896" d="M14,12 L15.5,12 C15.7761424,12 16,12.2238576 16,12.5 C16,12.7761424 15.7761424,13 15.5,13 L0.5,13 C0.223857625,13 3.38176876e-17,12.7761424 0,12.5 C-3.38176876e-17,12.2238576 0.223857625,12 0.5,12 L0.5,12 L2,12 L2,6 C2,2.6862915 4.6862915,6.08718376e-16 8,0 C11.3137085,-6.08718376e-16 14,2.6862915 14,6 L14,12 Z M3,12 L13,12 L13,6 C13,3.23857625 10.7614237,1 8,1 C5.23857625,1 3,3.23857625 3,6 L3,12 Z M8,16 C6.8954305,16 6,15.1045695 6,14 C7,14 9,14 10,14 C10,15.1045695 9.1045695,16 8,16 Z"></path>
</symbol>
<symbol viewBox="0 0 16 16" id="icon-arrow-up">
<path fill="#ffa896" d="M413.1,327.3l-1.8-2.1l-136-156.5c-4.6-5.3-11.5-8.6-19.2-8.6c-7.7,0-14.6,3.4-19.2,8.6L101,324.9l-2.3,2.6 C97,330,96,333,96,336.2c0,8.7,7.4,15.8,16.6,15.8v0h286.8v0c9.2,0,16.6-7.1,16.6-15.8C416,332.9,414.9,329.8,413.1,327.3z"></path>
</symbol>
</svg>
復(fù)制代碼
<svg>
<use xlink:href="#icon-arrow-up"/>
</svg>
<svg>
<use xlink:href="#icon-bell"/>
</svg>
復(fù)制代碼

symbol可以寫入到body中,同時(shí)可以使用外鏈引用,不過使用外鏈的方式在IE下兼容性不是很好。
<svg>
<use xlink:href="/assets/svg-symbols.svg#icon-arrow-up"/>
</svg>
復(fù)制代碼
工程化方式
手動(dòng)中合成引入那一坨 symbol 模板是不方便的,我們需要的是自動(dòng)管理圖標(biāo)。隨著 webpack 打包的成熟,各種 loader的出現(xiàn),為我們提供了成熟的方案。
svg-sprite-loader
針對(duì)所引用的svg文件,svg-sprite-loader會(huì)把你的icon塞到一個(gè)個(gè)symbol中,最終在你的body中嵌入合并后的symbol。
示例項(xiàng)目使用vue cli 3搭建,vue.config.js相關(guān)配置如下,我們會(huì)將圖標(biāo)文件放在一個(gè)特定的目錄中,針對(duì)該目錄下的文件,會(huì)將默認(rèn)的loader配置排除,使用svg-sprite-loader。
chainWebpack:config=> {
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
}
復(fù)制代碼
批量引入圖標(biāo)文件
const req = require.context('/src/icons', true, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)
復(fù)制代碼
運(yùn)行項(xiàng)目后可以看到body中插入了svg symbol

我們就可以在頁(yè)面中使用這些圖標(biāo)了,同時(shí)可以封裝通用icon調(diào)用組件,將圖標(biāo)name,size,color都作為參數(shù),就能方便控制圖標(biāo)的使用。
<template>
<i :style="styles" :class="iconClass">
<svg class="svg-icon" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</i>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
type: String,
size: [Number, String],
color: String,
className:String
},
computed: {
iconName() {
return `#icon-${this.type}`
},
iconClass() {
if (this.className) {
return 'icon ' + this.className
} else {
return 'icon'
}
},
styles() {
let style = {}
if (this.size) {
style['font-size'] = `${this.size}px`
}
if (this.color) {
style.color = this.color
}
return style
},
},
}
</script>
<style scoped>
.icon{
display: inline-block;
line-height: 0;
}
.svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
vertical-align: middle;
}
</style>
復(fù)制代碼
<svg-icon type="bell" size="60" color="#ffa896"/>
復(fù)制代碼

小結(jié)
各方案有利有弊,一套方案不一定能兼容全部場(chǎng)景,這一切都取決于項(xiàng)目實(shí)際情況和瀏覽器的支持情況,符合具體使用場(chǎng)景的解決方案才是好方案。
