在实际应用中,图标的使用无处不在,小到简书的编译页面,大到chrome浏览器的任务栏等,都有大量的图标需要处理,那如果我们自己的应用里也需要使用代表各种含义的图标时,我们应该怎么处理呢?是每一个图标都引入一张图片来做吗?
当然不是 。
利用react,我将试图创建一个Icon组件,我们可以在使用时,控制图标的颜色,大小,旋转,图标类型等。大概如下:
1<Icon color="red" size="small" type="skip" />2<Icon color="#FFF" size="small" type="loading" />
那么我们应该怎么实现呢?
我通常更喜欢使用预编译工具sass来代替css,如果读者朋友们还没有了解过它,可以花10分钟时间搜索学习,非常的简单。
首先下载sass-loader, node-sass
> yarn add sass-loader node-sass
然后修改webpack的配置如下:
1{2test: /\.s[ac]ss$/,3include: paths.appSrc,4loaders: ["style-loader", "css-loader", "sass-loader"]5},
注意,随着版本的更新,方式可能有所调整,以create react app 官方文档为准
重启项目即可生效。
最初见到字体图标的应用,还是在淘宝网站上。神奇的发现有的图标居然可以像字体一样,随意的给它设置颜色大小等属性。而到了现在,字体图标早已不是什么黑科技了,它几乎被普及到了所有网站。
在css3中,有一个语法可以自定义字体@font-face
。而这些字体库如果是由图标组成,那么我们就可以创建字体图标了。字体图标与文字具有相同的特性,我们可以把图标当成字体一样处理。例如修改它的font-size,color等。对应的css语法如下:
1@font-face {2font-family: 'custom name', /* 自定义字体名字 */3src: url('./fonts/custom.eot') /* 下载到本地的字体库 */4}
通常情况下,字体库中,每一个图标,都会对应一个唯一的标识码。现在我们要通过字体图标网站iconfont收集一个自己项目中会涉及到的图标。然后组成一个图标库。
点击第一个购物车图标,即可将图标收集。按需收集一些图标,统一添加到一个项目中。
可以使用线上图标库。点击查看在线链接并且生成代码即可。我的项目生成的在线代码如下:
1@font-face {2font-family: 'iconfont'; /* project id 496908 */3src: url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.eot');4src: url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.eot?#iefix') format('embedded-opentype'),5url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.woff') format('woff'),6url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.ttf') format('truetype'),7url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.svg#iconfont') format('svg');8}
将这段代码贴到我们的css文件中,就已经自定义了一个font-family为iconfont的字体图标。我们也可以将字体图标库下载下来,把url中的路径都修改为对应的字体库文件就行。
可以看到,每一个图标除了有一个对应的名字之外,还有一个唯一的unicode码。&#x表示他们后面跟的是16进制数字。假设我们期望在html中放入一个代表图标的标签。
<i class="icon-loading" />
那么,只要它对应的css这样写,就可以在页面中显示出字体库中的图标。
1.icon-loading {2font-family: "iconfont";3color: red;4font-size: 20px;5}6.icon-loading:before {7content: "\e602";8}
content的值是一个斜杠加上图标对应的十六进制数字。运行之后我们就能在页面中看到一个红色的斜体loading图标。
对应的scss写法为:
1/* 对应的scss写法为 */2.icon-loading {3font-family: "iconfont";4color: red;5font-size: 20px;6&:before {7content: "\e602";8}9}
很显然图标组件的封装不会涉及到太过于复杂的JS逻辑处理,更多的是对外部状态props的判断与处理。基础元素可以指定一个i标签。图标通过before/after伪类中的content显示。实现方法我们将每一个图标都对应写一个class,然后根据传入的type类型,动态的修改对应的class即可。
例如
10/* loading */20.icon-loading {30/* ... */40&:before { content: '\e602' }50}6070.icon-refresh {80/* ... */90&:before { content: '\e6aa' }10}
js的逻辑的处理主要是根据传入的参数,判断有哪些class名应该存在。
先思考一下组件封装好后,我们会遇到哪些情况。
第一个基本情况,就是简单的传入type,得到对应的图标显示。
<Icon type="close" />
第二种情况,是组件本身需要设置一些样式,因此可能会有通过添加class的方式定义css样式。
<Icon className="close" type="close" />
第三种情况,则是要直接修改图标的样式,例如设置颜色,字体大小等。
1// 第一种可以直接传入对应的属性2<Icon type="close" color="red" />34// 另外一种是利用jsx支持的style语法,传入css样式。5const style = {6color: 'red',7fontSize: '20px'8}9<Icon type="close" style={style} />
第四种情况是我们要考虑特殊的类型,例如loading图标需要一直旋转。例如refresh刷新图标,点击时才旋转,刷新完成就停止旋转。因此我们要专门针对这种情况做特殊处理。添加一个控制选择的属性。
1// 通过对spin的修改,来控制图标是否旋转2<Icon type="refresh" spin={true} />
其余的我们可能在实践中还会添加新的需求,到时候再根据需求做改进即可。
OK,带着这些基础知识和需求,我们开始动手来完成我们的第一个正式的React组件。
在src目录下,创建一个专门用来存放组件的文件夹,components。然后在components目录下创建Icon目录。并分别创建index.jsx与style.scss。我们将字体图标下载下来,存放于Icon目录的fonts目录中。
最终的文件结构大致如下:
1+ Icon2+ fonts3- index.jsx4- style.scss
通过上面的分析我们知道,基础元素的class可能会涉及到很多个,如果通过if/else来判断的话,可能我们的代码可读性会非常的低。因此这里我们借助一个专门处理class名的工具方法来完成逻辑的判断。这个工具库叫做classnames。
我们先安装这个库,然后重启项目。
> yarn add classnames
该工具方法的使用比较简单,它的目的在于拼接class名。
10import classnames from 'classnames';2030// 拼接所有参数40classnames('foo', 'bar'); // 'foo bar'5060// 拼接值为true的参数70classnames({80foo: true,90bar: false10}) // 'foo'1112// 也可以比较随意的混合使用13classnames('foo', {14bar: true,15tag: true,16mm: false17}) // 'foo bar tag'
更具体的用法可以查看npm中的文档
现在我们先来实现index.jsx中的代码编写。
首先引入必要的模块。
1import React from 'react';2import classnames from 'classnames';3import './style.scss';
然后给可能会接收的props设定一个默认值。
1const defaultProps = {2type: '',3spin: false4}
定义组件,因为仅仅只是一个UI展示,所以该组件是一个无状态组件,我们用function的方式来定义即可。
上一章主要介绍的是有状态的创建方式,没有涉及到这种方式,不过可以通过该例子直接掌握,不再特别描述
10const Icon = (props = defaultProps) => {20// 依次从props中取出可能会出现的值,此处的other表示其余所有剩余的属性,这是ES6的语法30const { type, className, spin, color, style, ...other } = props;4050// 利用classnames方法计算出最终的classname字符串。60const cls = classnames({70'icon': true,80'icon-spin': !!spin || type === 'loading',90[`icon-${type}`]: true10}, className);1112const _style = { ...style, color };1314return (15<i className={cls} {...other} style={_style} />16)17}
完整代码为
10import React from 'react';20import classnames from 'classnames';30import './style.scss';4050const defaultProps = {60type: '',70/**80* 是否旋转90*/10spin: false11}1213const Icon = (props = defaultProps) => {14// 依次从props中取出可能会出现的值,此处的other表示其余所有剩余的属性,这是ES6的语法15const { type, className, spin, color, style, ...other } = props;1617// 利用classnames方法计算出最终的classname字符串。18const cls = classnames({19'icon': true,20'icon-spin': !!spin || type === 'loading',21[`icon-${type}`]: true22}, className);2324const _style = { ...style, color };2526return (27<i className={cls} {...other} style={_style} />28)29}3031export default Icon;32
因为涉及到比较多的ES6语法的使用,如果还有ES6语法掌握不够熟练的同学,可以花半个小时熟悉一下ES6的的基本语法。前几个例子,我会尽量注释,等多几个例子之后,我就可能不会注释太多,相信到时候大家对ES6的掌握也比较熟练了。
接来下是css的实现。
10/**20* 图标项目地址30* http://iconfont.cn/manage/index?spm=a313x.7781069.1998910419.db775f1f3&manage_type=myprojects&projectId=597416&keyword=40*/5060@font-face {70font-family: "iconfont";80src: url('./fonts/iconfont.eot?t=1543309990807'); /* IE9*/90src: url('./fonts/iconfont.eot?t=1543309990807#iefix') format('embedded-opentype'), /* IE6-IE8 */10url('./fonts/iconfont.ttf?t=1543309990807') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/11url('./fonts/iconfont.svg?t=1543309990807#iconfont') format('svg'); /* iOS 4.1- */12}1314.icon {15font-family: "iconfont" !important;16font-size: 16px;17font-style: normal;18display: block;19-webkit-font-smoothing: antialiased;20-moz-osx-font-smoothing: grayscale;21}2223.icon-add::before { content: '\e65b' }24.icon-step:before { content: '\e65a'; }25.icon-complete:before { content: '\e658'; }26.icon-clock:before { content: '\e67d'; }27.icon-selected:before { content: '\e674'; }28.icon-phone:before { content: '\e67c'; }29.icon-share:before { content: '\e67b'; }30.icon-advisory:before { content: '\e67a'; }31.icon-car:before { content: '\e679'; }32.icon-star:before { content: '\e678'; }33.icon-starno:before { content: '\e677'; }34.icon-count:before { content: '\e676'; }35.icon-add2:before { content: '\e675'; }36.icon-check:before { content: '\e673'; }37.icon-gift:before { content: '\e672'; }38.icon-programe:before { content: '\e671'; }39.icon-order:before { content: '\e670'; }40.icon-adviser:before { content: '\e66f'; }41.icon-activity:before { content: '\e66e'; }42.icon-coupon:before { content: '\e66d'; }43.icon-aboutus:before { content: '\e66c'; }44.icon-envelope:before { content: '\e66b'; }45.icon-eyehide:before { content: '\e66a'; }46.icon-category:before { content: '\e669'; }47.icon-volume:before { content: '\e668'; }48.icon-pk:before { content: '\e667'; }49.icon-slider:before { content: '\e664'; }50.icon-scenes:before { content: '\e661'; }51.icon-full:before { content: '\e660'; }52.icon-eyeshow:before { content: '\e65f'; }53.icon-forward:before { content: '\e65e'; }5455.icon-spin {56animation-name: rotate;57animation-duration: 1s;58animation-timing-function: linear;59animation-iteration-count: infinite;60}6162@keyframes rotate {63from {64transform: rotate(0)65}66to {67transform: rotate(360deg)68}69}
OK,一个简单却非常nice的Icon组件就这样完成了。
我相信对于刚接触React的朋友来说,特别是对ES6运用不太熟练的朋友,这个例子虽然简单,却有许多值得总结的经验,建议大家动手实践,细细回味。淡化对React的紧张感与重视感,从大局去体会React的魅力,这比React本身的知识点更为重要。
最后写一个例子来简单瞄一眼我们的图标组件
10import React from 'react';20import ReactDOM from 'react-dom';30import Icon from 'components/Icon';40import { icons } from 'components/Icon/config';5060const container = {70display: 'flex',80maxWidth: '300px',90margin: 'auto',10justifyContent: 'space-around',11flexWrap: 'wrap'12}1314const itemContainer = {15width: '40px',16height: '40px',17display: 'flex',18justifyContent: 'center',19alignItems: 'center'20}2122class App extends React.Component {23render() {24return (25<div style={container}>26<Icon type="add" spin />27{icons.map((item, i) => (28<div key={i} style={itemContainer}>29<Icon type={item} />30</div>31))}32</div>33)34}35}3637ReactDOM.render(<App />, document.getElementById('root'));