前端框架多吗? 多! 前端UI组件库多吗? 更多! 咱们都知道,前端生态圈里提供了各色各样的组件库供咱们选择使用,大多数都能知足开发者的需求,相信你们也都用过不少。可是实际上,据我了解到的,稍微大一些有本身产品的公司都会有一套自定义的UI组件库,知足自身复杂的需求与绚丽的效果。 博主目前所在的公司也有一套本身的产品,PC端所用的前端框架是Angular4. Angular4其实也有它专门定制的前端组件库PrimeNg.就像Vue.js有Element同样. 那么按理在开发中咱们已经有了前端组件库可使用,为何还要花那么多的精力和时间来从新设计UI组件呢?话很少说,先上几张官方提供的UI组件图: css
::ng-deep
来进行对组件样式的修改,但修改起来仍是比较麻烦。有那时间,本身都已经撸了一个了... (项目中用的
primeng
是
v4.2.2
版本的,目前已经迭代到了
v6.1.5
,因此如今官网上看到的
inputSwitch
组件会比这个好看点) emmm....为了追求用户体验(呸,熟悉
angular4
的使用)因此博主决定利用闲暇之余自定义一些UI组件,以知足咱们产品"一些无礼的要求"。
一个项目中会有各类文件、文件夹,如何存放管理好这些文件真的很重要。不只为本身提供了方便,也为后来的开发者提供方便。 因此咱们在设计公用组件的时候也应该把它们都归结在一块儿。 我习惯在项目中新建一个common
文件夹,里面存放一些共用的compoent
,service
等等。 html
common
文件夹下导出的是一个名为
shared
的模块。
shared
模块的建立过程: (1)打开命令行(使用vscode编辑器的小伙能够直接使用Ctrl+` 快捷键打开终端,而后一路跳转到common文件夹:
cd src\app\common
复制代码
(2) 使用建立模块的指令:前端
ng g m shared
复制代码
其实很好理解:ng
为angular
一向的指令,g
为generate
建立的缩写,m
为module
模块的缩写,后面接着你的模块名。(后面建立组件也是这个原理) 建立的模块实际上导出的是一个带有@NgModule
装饰器的类而已,其中提供了咱们自定义的公有组件component
,公有服务service
,以及管道pipe
等等。vue
因为咱们要建立的是一个switch
公用组件,因此在component
文件夹下在建立一个文件夹general-control
,以前都是直接堆积在component
文件夹下的,近期发现堆得有点多了,因此又单首创建了一个general-control
文件夹来存放一些基础的公用组件。 此时你须要打开命令行(使用vscode编辑器的小伙能够直接使用Ctrl+` 快捷键打开终端,而后一路跳转到general-control文件夹:sass
cd src\app\common\component\general-control
复制代码
在此目录下执行指令:bash
ng g c switch
复制代码
上面指令的意思是建立一个名为switch
的组件,原理和建立模块时同样。 能够看到如今的general-control
文件夹下多出了一些东西: 前端框架
switch
组件。 指令会自动帮你生成一个文件夹和4个文件。(基于
TypeScript
的语法,因此生成的
js
文件也就是
ts
) 很好理解,对应的
html
文件编写
HTML代码
,
css
文件编写
CSS代码
,
ts
文件编写
js
代码,至于
spec.ts
文件咱们能够不用管它。 因为我在项目中使用的是
sass,因此将
switch.component.css
这个文件的后缀名修改成
scss
(使用了less等其它扩展语言的小伙同理),并在
ts
中对
css
的引用进行修改:
使用上面的指令建立的组件是会被自动引用到shared
这个模块中的。 shared.module.ts
:app
import { SwitchComponent } from './component/general-control/switch/switch.component';//模块中import引入组件
@NgModule({
declarations: [
SwitchComponent //模块中声明组件
...
]
})
复制代码
上面俩步是你在使用ng g c switch
指令时自动帮你完成的,但如果你想在其它的模块中使用这个switch
组件,还得将其导出,导出的方式是将这个组件添加至shared.module.ts
文件的exports
中:框架
import { SwitchComponent } from './component/general-control/switch/switch.component';//模块中import引入组件
@NgModule({
declarations: [
SwitchComponent //模块中声明组件
...
],
exports: [
SwitchComponent //模块中导出组件
...
]
})
复制代码
完成上面的步骤你就能够安心的来开发本身的组件了。less
一番查找,发现网上也有不少自定义switch
组件的文章和源码,多是你们都以为原生的样式很差看吧... 有使用input
而后来进行修改样式的,也有用其它标签来自定义的。 博主这里找了一个最简单方案,一个span
标签搞定:
// switch.component.html
<span class="weui-switch" [ngClass]="currentClass" [ngStyle]="style" (click)="toggle()">
</span>
复制代码
基础css
// switch.comonent.scss
.weui-switch {
display: inline-block;
position: relative;
width: 38px;
height: 23px;
border: 1px solid #DFDFDF;
outline: 0;
border-radius: 16px;
box-sizing: border-box;
background-color: #DFDFDF;
transition: background-color 0.1s, border 0.1s;
cursor: pointer;
&.disabled{
opacity: 0.6;
cursor: not-allowed;
}
}
.weui-switch:before {
content: " ";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 15px;
background-color: #FDFDFD;
transition: transform 0.35s cubic-bezier(0.45, 1, 0.4, 1);
}
.weui-switch:after {
content: " ";
position: absolute;
top: 0;
left: 0;
width: 56%;
height: 97%;
border-radius: 15px;
background-color: #FFFFFF;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
transition: transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35);
}
.weui-switch-on {
border-color: #1AAD19;
background-color: #1AAD19;
}
.weui-switch-on:before {
border-color: #1AAD19;
background-color: #1AAD19;
}
.weui-switch-on:after {
transform: translateX(77%);
}
复制代码
效果大概就是这个样子:
:after
和
:before
来实现的,而开关的效果是经过点击的时候添 加/移除
class
名
weui-switch-on
来实现的。(讲js的时候会讲到)
因为咱们建立的switch
组件是须要在多处使用,而且要向外输出一些值,因此在ts
中咱们首先要引入一下@Input
、@Output
装饰器和EventEmitter
。
import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';
复制代码
而且定义一些基础的变量
@Input() style;//{ 'width': '40px' }//外部组件输入的样式对象
@Input() isChecked: boolean = false;//开关是否打开
@Input() disabled: boolean = false;//开关是否被禁用
@Output() change: EventEmitter<any> = new EventEmitter();
_isSwitch: boolean = false;
currentClass = {}
复制代码
此时咱们的ts
变成了这样:
import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';
@Component({
selector: 'app-switch',
templateUrl: './switch.component.html',
styleUrls: ['./switch.component.scss']
})
export class SwitchComponent implements OnInit, OnChanges {
constructor() { }
@Input() style;//{ 'width': '40px' }//外部组件输入的样式对象
@Input() isChecked: boolean = false;//外部组件输入进来的:开关是否打开
@Input() disabled: boolean = false;//开关是否被禁用
@Output() change: EventEmitter<any> = new EventEmitter();
_isSwitch: boolean = false;//switch组件自己的:开关是否打开
currentClass = {} //class集合
ngOnInit() {//初始化组件的生命周期
}
ngOnChanges() {//当被绑定的输入属性的值发生变化时调用
}
}
复制代码
组件中定义了俩个“开关是否打开”的变量isChecked
和_isSwitch
一个是外部组件传递进来的默认值,一个是 switch
组件自身的值。 因此在组件进行初始化和发生改变的时候咱们应该让其统一:
ngOnInit() {//初始化组件的生命周期
this.setIsSwitch();
}
ngOnChanges() {//当被绑定的输入属性的值发生变化时调用
this.setIsSwitch();
}
setIsSwitch() {//设置_isSwitch
this._isSwitch = this.isChecked;
}
复制代码
因为是自定义的组件,咱们固然是但愿大小也能够自定义,因此我想要的效果是: 在调用组件的时候,输入一个宽度width
属性,组件可以自动调节尺寸。 所以我在设计的时候就定义了一个style
变量 它是一个对象,能够容许开发者输入任意的样式,格式为{ 'width': '40px' }
同时为了减小输入样式的复杂度,咱们还能够来编写一个方法,让组件可以根据宽度来调节高度:
setStyle() {//设置样式
if (this.style) {
if (this.style['width'] && !this.style['height']) {//如果输入了宽度没有输入高度则自动计算
let width = this.getWidth(this.style['width']);
this.style['height'] = (width * 0.55) + 'px';
}
}
}
getWidth(widthStr) {//判断用户输入的width带不带px单位
let reg = /px/;
let width = reg.test(widthStr) ? widthStr.match(/(\d*)px/)[1] : widthStr //正则获取不带单位的值
if (!width) width = 0;
return width;
}
复制代码
能够看到,上面我编写的setStyle()
方法是判断有没有宽度和高度,并将高度设置为0.55 * width
(0.55为我找到的最合适的比例)
完成了上面的步骤咱们基本就完成了对组件样式的初始化,可是,最重要的一步固然是经过添加/移除一些类来进行组件的交互:
setClass() {//转换switch时切换class
this.currentClass = {
'disabled': this.disabled,
'bg_main bor_main weui-switch-on': this._isSwitch
}
}
复制代码
对象currentClass
存储的是组件变更的类名,对象的键名为类名,值为一个布尔类型的变量(true / false) 经过布尔类型的变量来判断添加仍是移除这些类名。 第一个类disabled
表示的是开关是否被禁用,也就是用户只能查看开关,并不能对其进行操做,它受disabled
变量控制。 第二个类为三个类名的合写bg_main
、bor_main
、和weui-switch-on
,他们受_isSwitch
变量控制, 也就是开关打开的时候则添加这三个类。 前俩个类名是我在项目中使用的“皮肤类名”,由于客户的须要,咱们产品有几套不一样的主题色,用户能够进行换肤功能来切换主题色,所以就有一些类名须要用来控制主题色。 如橘色主题:
.bg_main {
background-color: #ff7920!important;
}
.bor_main {
border-color: #ff7920!important;
}
复制代码
固然,你如果没有主题色的话请忽略这俩个类。
上面的几个方法咱们都须要在组件初始化和变量发生改变的时候调用,因此能够整合到一个函数中:
ngOnInit() {
this.initComponent();
}
ngOnChanges() {
this.initComponent();
}
initComponent() {
this.setIsSwitch();
this.setStyle();
this.setClass();
}
复制代码
光有样式可没用,咱们还须要将组件和用户的行为给结合在一块儿,所以给组件一个click
事件来进行交互,并编写toggle()
方法:
toggle() {//切换switch
if (this.disabled) return;//如果禁用时则直接返回
this._isSwitch = !this._isSwitch;
this.isChecked = this._isSwitch;
this.change.emit(this._isSwitch); //向外部传递最新的值
}
复制代码
整合后的ts
文件为这样:
import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';
@Component({
selector: 'app-switch',
templateUrl: './switch.component.html',
styleUrls: ['./switch.component.scss']
})
export class SwitchComponent implements OnInit, OnChanges {
constructor() { }
@Input() onLabel: string = '';//暂无
@Input() offLabel: string = '';
@Input() style;//{ 'width': '40px' }//外部组件输入的样式对象
@Input() isChecked: boolean = false;//开关是否打开
@Input() disabled: boolean = false;//开关是否被禁用
@Output() change: EventEmitter<any> = new EventEmitter();
_isSwitch: boolean = false;
currentClass = {}
ngOnInit() {
this.initComponent();
}
ngOnChanges() {
this.initComponent();
}
initComponent() {//初始化并刷新组件
this.setIsSwitch();
this.setStyle();
this.setClass();
}
setIsSwitch() {
this._isSwitch = this.isChecked;
}
setStyle() {//设置样式
if (this.style) {
if (this.style['width'] && !this.style['height']) {//如果输入了宽度没有输入高度则自动计算
let width = this.getWidth(this.style['width']);
this.style['height'] = (width * 0.55) + 'px';
}
}
}
setClass() {//转换switch时切换class
this.currentClass = {
'disabled': this.disabled,
'bg_main bor_main weui-switch-on': this._isSwitch
}
}
getWidth(widthStr) {//判断用户输入的width带不带px单位
let reg = /px/;
let width = reg.test(widthStr) ? widthStr.match(/(\d*)px/)[1] : widthStr //正则获取不带单位的值
if (!width) width = 0;
return width;
}
toggle() {//切换switch
if (this.disabled) return;//如果禁用时则直接返回
this._isSwitch = !this._isSwitch;
this.isChecked = this._isSwitch;
this.change.emit(this._isSwitch);
}
}
复制代码
完成了上面的部分,到了咱们最激动的时候了,看看咱们亲手制做的组件有没有用吧,哈哈。 首先,在使用其它组件的时候,咱们要将其引入进来,因为咱们最开始是将switch
组件引入到shared
这个模块中,并从这个模块中导出的,因此想要在其它模块中使用 switch
组件就得先引入shared
模块。
本项目中有另外一个模块名为coursemanage
,如今我将其做为父组件来引用一下switch
组件 首先在模块里引用:
//coursemanage.module.ts
import { NgModule } from '@angular/core';
import { SharedModule } from "./../common/shared.module";
@NgModule({
imports: [
SharedModule
]
})
export class CourseManageModule { }
复制代码
引入了shared
模块就至关因而引入那个那个模块中的全部组件和方法。
在coursemanage
模块中,有其子组件course
这个组件,在course
中使用switch
<!--course.component.html-->
<app-switch [isChecked]="dataStatus" (change)="changeSwitch($event)"></app-switch>
复制代码
//course.component.ts
dataStatus: boolean = false;
changeSwitch($event) {
this.dataStatus = $event;
}
复制代码
此时就完成了switch
组件的编写和使用。 你也能够给组件设置另外一个属性disabled
:
<!--course.component.html-->
<app-switch [isChecked]="dataStatus" [disable]="true" (change)="changeSwitch($event)"></app-switch>
复制代码
上述设计的switch组件应该是UI组件中比较简单的一种UI组件了,还有更多复杂的组件有待咱们的开发,经过本身设计UI组件,emmm....可让咱们更有创造力吧应该说,也促使本身多去看别人的博客与源码,最后再写上一篇总结,我认为这应该是一个正向的激励💪,哈哈,全篇废话不少,不过仍是要感谢小伙的阅读🙂。