在實際應用中嚷闭,圖標的使用無處不在勤众,小到簡書的編譯頁面掩浙,大到chrome瀏覽器的任務欄等花吟,都有大量的圖標需要處理,那如果我們自己的應用里也需要使用代表各種含義的圖標時厨姚,我們應該怎么處理呢衅澈?是每一個圖標都引入一張圖片來做嗎?
當然不是 谬墙。
利用react今布,我將試圖創(chuàng)建一個Icon組件经备,我們可以在使用時,控制圖標的顏色部默,大小侵蒙,旋轉(zhuǎn),圖標類型等傅蹂。大概如下:
<Icon color="red" size="small" type="skip" />
<Icon color="#FFF" size="small" type="loading" />
那么我們應該怎么實現(xiàn)呢纷闺?
一、構建工具集成sass
我通常更喜歡使用預編譯工具sass來代替css份蝴,如果讀者朋友們還沒有了解過它急但,可以花10分鐘時間搜索學習,非常的簡單搞乏。
首先下載sass-loader, node-sass
> yarn add sass-loader node-sass
然后修改webpack的配置如下:
{
test: /\.s[ac]ss$/,
include: paths.appSrc,
loaders: ["style-loader", "css-loader", "sass-loader"]
},
注意波桩,隨著版本的更新,方式可能有所調(diào)整请敦,以create react app 官方文檔為準
重啟項目即可生效镐躲。
二、字體圖標
最初見到字體圖標的應用侍筛,還是在淘寶網(wǎng)站上萤皂。神奇的發(fā)現(xiàn)有的圖標居然可以像字體一樣,隨意的給它設置顏色大小等屬性匣椰。而到了現(xiàn)在裆熙,字體圖標早已不是什么黑科技了,它幾乎被普及到了所有網(wǎng)站禽笑。
在css3中入录,有一個語法可以自定義字體@font-face
。而這些字體庫如果是由圖標組成佳镜,那么我們就可以創(chuàng)建字體圖標了僚稿。字體圖標與文字具有相同的特性,我們可以把圖標當成字體一樣處理蟀伸。例如修改它的font-size蚀同,color等。對應的css語法如下:
@font-face {
font-family: 'custom name', /* 自定義字體名字 */
src: url('./fonts/custom.eot') /* 下載到本地的字體庫 */
}
通常情況下啊掏,字體庫中蠢络,每一個圖標,都會對應一個唯一的標識碼〕倜郏現(xiàn)在我們要通過字體圖標網(wǎng)站iconfont收集一個自己項目中會涉及到的圖標刹孔。然后組成一個圖標庫。
點擊第一個購物車圖標小泉,即可將圖標收集芦疏。按需收集一些圖標冕杠,統(tǒng)一添加到一個項目中。
可以使用線上圖標庫。點擊查看在線鏈接并且生成代碼即可薪捍。我的項目生成的在線代碼如下:
@font-face {
font-family: 'iconfont'; /* project id 496908 */
src: url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.eot');
src: url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.woff') format('woff'),
url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.ttf') format('truetype'),
url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.svg#iconfont') format('svg');
}
將這段代碼貼到我們的css文件中笼痹,就已經(jīng)自定義了一個font-family為iconfont的字體圖標。我們也可以將字體圖標庫下載下來酪穿,把url中的路徑都修改為對應的字體庫文件就行凳干。
可以看到,每一個圖標除了有一個對應的名字之外被济,還有一個唯一的unicode碼救赐。&#x表示他們后面跟的是16進制數(shù)字。假設我們期望在html中放入一個代表圖標的標簽只磷。
<i class="icon-loading" />
那么经磅,只要它對應的css這樣寫,就可以在頁面中顯示出字體庫中的圖標钮追。
.icon-loading {
font-family: "iconfont";
color: red;
font-size: 20px;
}
.icon-loading:before {
content: "\e602";
}
content的值是一個斜杠加上圖標對應的十六進制數(shù)字预厌。運行之后我們就能在頁面中看到一個紅色的斜體loading圖標。
對應的scss寫法為:
/* 對應的scss寫法為 */
.icon-loading {
font-family: "iconfont";
color: red;
font-size: 20px;
&:before {
content: "\e602";
}
}
三元媚、字體圖標組件
很顯然圖標組件的封裝不會涉及到太過于復雜的JS邏輯處理轧叽,更多的是對外部狀態(tài)props的判斷與處理】兀基礎元素可以指定一個i標簽炭晒。圖標通過before/after偽類中的content顯示。實現(xiàn)方法我們將每一個圖標都對應寫一個class鞠绰,然后根據(jù)傳入的type類型腰埂,動態(tài)的修改對應的class即可飒焦。
例如
/* loading */
.icon-loading {
/* ... */
&:before { content: '\e602' }
}
.icon-refresh {
/* ... */
&:before { content: '\e6aa' }
}
js的邏輯的處理主要是根據(jù)傳入的參數(shù)蜈膨,判斷有哪些class名應該存在。
先思考一下組件封裝好后牺荠,我們會遇到哪些情況翁巍。
第一個基本情況,就是簡單的傳入type休雌,得到對應的圖標顯示灶壶。
<Icon type="close" />
第二種情況,是組件本身需要設置一些樣式杈曲,因此可能會有通過添加class的方式定義css樣式驰凛。
<Icon className="close" type="close" />
第三種情況胸懈,則是要直接修改圖標的樣式,例如設置顏色恰响,字體大小等趣钱。
// 第一種可以直接傳入對應的屬性
<Icon type="close" color="red" />
// 另外一種是利用jsx支持的style語法,傳入css樣式胚宦。
const style = {
color: 'red',
fontSize: '20px'
}
<Icon type="close" style={style} />
第四種情況是我們要考慮特殊的類型首有,例如loading圖標需要一直旋轉(zhuǎn)。例如refresh刷新圖標枢劝,點擊時才旋轉(zhuǎn)井联,刷新完成就停止旋轉(zhuǎn)。因此我們要專門針對這種情況做特殊處理您旁。添加一個控制選擇的屬性烙常。
// 通過對spin的修改,來控制圖標是否旋轉(zhuǎn)
<Icon type="refresh" spin={true} />
其余的我們可能在實踐中還會添加新的需求鹤盒,到時候再根據(jù)需求做改進即可军掂。
OK,帶著這些基礎知識和需求昨悼,我們開始動手來完成我們的第一個正式的React組件蝗锥。
在src目錄下,創(chuàng)建一個專門用來存放組件的文件夾率触,components终议。然后在components目錄下創(chuàng)建Icon目錄。并分別創(chuàng)建index.jsx與style.scss葱蝗。我們將字體圖標下載下來穴张,存放于Icon目錄的fonts目錄中。
最終的文件結構大致如下:
+ Icon
+ fonts
- index.jsx
- style.scss
通過上面的分析我們知道两曼,基礎元素的class可能會涉及到很多個皂甘,如果通過if/else來判斷的話,可能我們的代碼可讀性會非常的低悼凑。因此這里我們借助一個專門處理class名的工具方法來完成邏輯的判斷偿枕。這個工具庫叫做classnames。
我們先安裝這個庫户辫,然后重啟項目渐夸。
> yarn add classnames
該工具方法的使用比較簡單,它的目的在于拼接class名渔欢。
import classnames from 'classnames';
// 拼接所有參數(shù)
classnames('foo', 'bar'); // 'foo bar'
// 拼接值為true的參數(shù)
classnames({
foo: true,
bar: false
}) // 'foo'
// 也可以比較隨意的混合使用
classnames('foo', {
bar: true,
tag: true,
mm: false
}) // 'foo bar tag'
更具體的用法可以查看npm中的文檔
現(xiàn)在我們先來實現(xiàn)index.jsx中的代碼編寫墓塌。
首先引入必要的模塊。
import React from 'react';
import classnames from 'classnames';
import './style.scss';
然后給可能會接收的props設定一個默認值。
const defaultProps = {
type: '',
spin: false
}
定義組件苫幢,因為僅僅只是一個UI展示访诱,所以該組件是一個無狀態(tài)組件,我們用function的方式來定義即可韩肝。
上一章主要介紹的是有狀態(tài)的創(chuàng)建方式盐数,沒有涉及到這種方式,不過可以通過該例子直接掌握伞梯,不再特別描述
const Icon = (props = defaultProps) => {
// 依次從props中取出可能會出現(xiàn)的值玫氢,此處的other表示其余所有剩余的屬性,這是ES6的語法
const { type, className, spin, color, style, ...other } = props;
// 利用classnames方法計算出最終的classname字符串谜诫。
const cls = classnames({
'icon': true,
'icon-spin': !!spin || type === 'loading',
[`icon-${type}`]: true
}, className);
const _style = { ...style, color };
return (
<i className={cls} {...other} style={_style} />
)
}
完整代碼為
import React from 'react';
import classnames from 'classnames';
import './style.scss';
const defaultProps = {
type: '',
/**
* 是否旋轉(zhuǎn)
*/
spin: false
}
const Icon = (props = defaultProps) => {
// 依次從props中取出可能會出現(xiàn)的值漾峡,此處的other表示其余所有剩余的屬性,這是ES6的語法
const { type, className, spin, color, style, ...other } = props;
// 利用classnames方法計算出最終的classname字符串喻旷。
const cls = classnames({
'icon': true,
'icon-spin': !!spin || type === 'loading',
[`icon-${type}`]: true
}, className);
const _style = { ...style, color };
return (
<i className={cls} {...other} style={_style} />
)
}
export default Icon;
因為涉及到比較多的ES6語法的使用生逸,如果還有ES6語法掌握不夠熟練的同學,可以花半個小時熟悉一下ES6的的基本語法且预。前幾個例子槽袄,我會盡量注釋,等多幾個例子之后锋谐,我就可能不會注釋太多遍尺,相信到時候大家對ES6的掌握也比較熟練了。
接來下是css的實現(xiàn)涮拗。
/**
* 圖標項目地址
* http://iconfont.cn/manage/index?spm=a313x.7781069.1998910419.db775f1f3&manage_type=myprojects&projectId=597416&keyword=
*/
@font-face {
font-family: "iconfont";
src: url('./fonts/iconfont.eot?t=1543309990807'); /* IE9*/
src: url('./fonts/iconfont.eot?t=1543309990807#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('./fonts/iconfont.ttf?t=1543309990807') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('./fonts/iconfont.svg?t=1543309990807#iconfont') format('svg'); /* iOS 4.1- */
}
.icon {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
display: block;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-add::before { content: '\e65b' }
.icon-step:before { content: '\e65a'; }
.icon-complete:before { content: '\e658'; }
.icon-clock:before { content: '\e67d'; }
.icon-selected:before { content: '\e674'; }
.icon-phone:before { content: '\e67c'; }
.icon-share:before { content: '\e67b'; }
.icon-advisory:before { content: '\e67a'; }
.icon-car:before { content: '\e679'; }
.icon-star:before { content: '\e678'; }
.icon-starno:before { content: '\e677'; }
.icon-count:before { content: '\e676'; }
.icon-add2:before { content: '\e675'; }
.icon-check:before { content: '\e673'; }
.icon-gift:before { content: '\e672'; }
.icon-programe:before { content: '\e671'; }
.icon-order:before { content: '\e670'; }
.icon-adviser:before { content: '\e66f'; }
.icon-activity:before { content: '\e66e'; }
.icon-coupon:before { content: '\e66d'; }
.icon-aboutus:before { content: '\e66c'; }
.icon-envelope:before { content: '\e66b'; }
.icon-eyehide:before { content: '\e66a'; }
.icon-category:before { content: '\e669'; }
.icon-volume:before { content: '\e668'; }
.icon-pk:before { content: '\e667'; }
.icon-slider:before { content: '\e664'; }
.icon-scenes:before { content: '\e661'; }
.icon-full:before { content: '\e660'; }
.icon-eyeshow:before { content: '\e65f'; }
.icon-forward:before { content: '\e65e'; }
.icon-spin {
animation-name: rotate;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes rotate {
from {
transform: rotate(0)
}
to {
transform: rotate(360deg)
}
}
OK乾戏,一個簡單卻非常nice的Icon組件就這樣完成了。
我相信對于剛接觸React的朋友來說三热,特別是對ES6運用不太熟練的朋友鼓择,這個例子雖然簡單,卻有許多值得總結的經(jīng)驗就漾,建議大家動手實踐呐能,細細回味。淡化對React的緊張感與重視感抑堡,從大局去體會React的魅力摆出,這比React本身的知識點更為重要。
最后寫一個例子來簡單瞄一眼我們的圖標組件
import React from 'react';
import ReactDOM from 'react-dom';
import Icon from 'components/Icon';
import { icons } from 'components/Icon/config';
const container = {
display: 'flex',
maxWidth: '300px',
margin: 'auto',
justifyContent: 'space-around',
flexWrap: 'wrap'
}
const itemContainer = {
width: '40px',
height: '40px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}
class App extends React.Component {
render() {
return (
<div style={container}>
<Icon type="add" spin />
{icons.map((item, i) => (
<div key={i} style={itemContainer}>
<Icon type={item} />
</div>
))}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));