封装一个图标组件,可以配置某一个图标,可以设置是否旋转,可以追加字体图标。特点简单实用,灵活可配。
一个 Vue 的文件
共有 class
封装一个图标组件首先从一个 vue 文件开始,其主要的结构就是一个 i
标签。最关键的就是这个 class 名的配置。有一个共有的 w-icon 类名负责自定义字体,这个字体就是从字体图标工具中下载的包括自定义字体图标的字体,还包括一些共有的样式。
定义字体图标的 class
光有一个共有的类名可不行,不同的字体图标是通过不同的 class 名配置的,我们得需要一个 prop 来设置显示的字体图标,我们把这个 prop 设定为 type
,类型为 String
。如果不传类型的时候,我们就不显示这个 icon 。
设定是否旋转的 class
这里有一个高级功能那就是可配置的旋转动画。我们需要一个 prop 来设置是否需要旋转动画,我们把这个 prop 设定为 spin
,类型为 Boolean
。如果为 true ,那么我们就让它转起来。
设定 class 的前缀参数
w-icon 默认前缀为 w
,如果您需要扩展字体图标,或者需要自定义样式,我们为您提供了一个 prop 。专为此事而行,这个 prop 我们设定为 prefix
,类型为 String
,默认为 w
。
vue 组件源码奉上
<template> <i v-if="type" :class="[`${prefix}-font`,`${prefix}-${type}`, { 'w-spin': !!spin, }]" ></i> </template> <script> export default { name: 'WIcon', props: { type: String, spin: Boolean, prefix: { type: String, default: 'w', }, }, data() { return {}; }, }; </script>
一个 SCSS 的文件
我们已经有一个像样的 vue 文件,那么我们还需要让 vue 的 icon 组件美起来的样式文件。这里我们选择的预处理器为 SCSS 。在这个样式文件中,我们需要做三件事 ⤵️
- 声明字体图标的字体
- 设置共有样式 w-font
- 设置字体图片类型的众多样式,如 w-loading 等
- 设置旋转动画类型
w-spin
- 设置旋转动画
声明字体图标的字体
这里我们利用 CSS3 的自定义字体的属性来完成这项使命。需要注意的是根据不同浏览器支持的字体后缀来做好适配。
设置共有样式 w-font
声明好字体之后,就可以用了。用法和普通设置字体是一样的,名字就用之前咱们设置好的就可以了。除了设置字体之外,我们还需要做一些特殊处理。如居中等。设置 text-rendering 为 optimizeLegibility 。它使得对于某些字体(例如,Microsoft的Calibri,Candara,Constantia和Corbel或DejaVu字体系列),文本中的连字(ff,fi,fl等)小于20px。
设置字体图片类型的众多样式
这个就是根据导出的字体图标,在 before 伪元素中设置字体图标的内容即可。如果有共同特点,如 w-loading1 , w-loading2 等,可以实用 sass 中的循环来优化写法。
设置旋转动画类型 w-spin
这里使用了 CSS3 中的 动画属性 (animation) 。让它执行 loadingCircle 动画,并且无线的匀速执行下去,每次动画时间为 1 秒。
设置旋转动画
利用 @keyframes
关键字声明一个动画,名为 loadingCircle 。在最开始的时候旋转基点定为图标中心点,并旋转角度为 0 度。在最结束的时候定义旋转基点定为图标中心点,并旋转角度为 1 圈( trun )。
scss 样式源码奉上
$font-name: "iconfont"; $font-version: 1524475512080; $font-prefix: 'w-'; $font-path: './icon/font/'; @font-face { font-family: $font-name; src: url('#{$font-path}iconfont.eot?t=#{$font-version}'); src: url('#{$font-path}iconfont.eot?t=#{$font-version}#iefix') format('embedded-opentype'), url("#{$font-path}iconfont.woff?t=#{$font-version}") format("woff"), url('#{$font-path}iconfont.ttf?t=#{$font-version}') format('truetype'), url('#{$font-path}iconfont.svg?t=#{$font-version}#iconfont') format('svg'); } .#{$font-prefix}font { font-family: $font-name !important; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-block; font-style: normal; vertical-align: baseline; text-align: center; text-transform: none; line-height: 1; text-rendering: optimizeLegibility; } .#{$font-prefix}back::before { content: "\e934"; } .#{$font-prefix}forward::before { content: "\e94d"; } $font-list: "\e66a" "\e668" "\e669" "\e667"; // 循环 $font-list 并设置类名 // 其中 index 方法是获取索引 @each $font-item in $font-list { .#{$font-prefix}loading#{index($font-list, $font-item)}::before { content: $font-item; } } .#{$font-prefix}spin::before { display: inline-block; animation: loadingCircle 1s infinite linear; } @keyframes loadingCircle { 0% { transform-origin: 50% 50%; transform: rotate(0deg); } to { transform-origin: 50% 50%; transform: rotate(1turn); } }
注册 w-icon 组件
要注册一个组件,必须这个组件中拥有 install
方法。有了这个方法,在其回调中的 Vue 参数中调用 component
方法,即可。方法如下 ⤵️
import WIcon from './Icon'; WIcon.install = (Vue) => { Vue.component(WIcon.name, WIcon); }; export default WIcon;
写一个图标组件的单元测试
单元测试一个图标组件,我们需要测试其属性是否真正用到了,是否与预期的一样。我们需要测试其快照的 UI 是否是我们想要的 UI 。我们不传 type 属性不显示的特殊处理是否真的生效。我们自定义前缀是否真的自定义了。
我们在一开始需要实例化组件,我们可以用 vue-test-utils
中的 mount
方法。当 DOM 更新之后, this
绑定到当前实例上之后( $nextTick ) 做一些事情。
具体测试代码 ⤵️
import { mount } from 'vue-test-utils'; import Icon from './Icon'; describe('Icon.vue', () => { let wrapper = null; let wrapperNo = null; let wrapperPrefix = null; let wrapperSpin = null; // 实例化 beforeEach(() => { // 传入 loading1 ,然后去检测实例化之后 type 是否是 loading1 。 wrapper = mount(Icon, { propsData: { type: 'loading1', }, }); // 不向其传任何参数,去检测针对 type 为空不显示的特殊处理是否生效。 wrapperNo = mount(Icon); // 传前缀 test ,测试实例化之后的前缀是否完全改变 wrapperPrefix = mount(Icon, { propsData: { type: 'loading1', prefix: 'test', }, }); // 实例化之后图标是否拥有旋转动画 wrapperSpin = mount(Icon, { propsData: { type: 'loading1', spin: true, }, }); }); it('验证 prop 值是否正确', (done) => { wrapper.vm.$nextTick(() => { try { expect(wrapper.props().type).toBe('loading1'); done(); } catch (err) { done.fail(err); } }); }); it('验证 class 值是否正确', (done) => { wrapper.vm.$nextTick(() => { try { expect(wrapper.is('i')).toBe(true); expect(wrapper.classes().toString()).toBe('w-font,w-loading1'); done(); } catch (err) { done.fail(err); } }); }); it('验证 prop 为空不显示', (done) => { wrapperNo.vm.$nextTick(() => { try { expect(wrapperNo.is('i')).toBe(false); done(); } catch (err) { done.fail(err); } }); }); it('验证 自定义前缀 prefix', (done) => { wrapperPrefix.vm.$nextTick(() => { try { expect(wrapperPrefix.is('i')).toBe(true); expect(wrapperPrefix.classes().toString()).toBe('test-font,test-loading1'); done(); } catch (err) { done.fail(err); } }); }); it('验证 spin 字段转动', (done) => { wrapperSpin.vm.$nextTick(() => { try { expect(wrapperSpin.is('i')).toBe(true); expect(wrapperSpin.classes().toString()).toBe('w-font,w-loading1,w-spin'); done(); } catch (err) { done.fail(err); } }); }); it('检测快照是否一样。', (done) => { wrapper.vm.$nextTick(() => { try { // 检测 UI 是否改变 expect(wrapper.text()).toMatchSnapshot(); done(); } catch (err) { done.fail(err); } }); }); });
源码
更完整的源码,请移步 w-icon 。