前端框架多嗎? 多! 前端UI組件庫多嗎? 更多! 咱們都知道,前端生態圈裏提供了各色各樣的組件庫供咱們選擇使用,大多數都能知足開發者的需求,相信你們也都用過不少。可是實際上,據我瞭解到的,稍微大一些有本身產品的公司都會有一套自定義的UI組件庫,知足自身複雜的需求與絢麗的效果。 博主目前所在的公司也有一套本身的產品,PC端所用的前端框架是Angular4. Angular4其實也有它專門定製的前端組件庫PrimeNg.就像Vue.js有Element同樣. 那麼按理在開發中咱們已經有了前端組件庫可使用,爲何還要花那麼多的精力和時間來從新設計UI組件呢?話很少說,先上幾張官方提供的UI組件圖: css
確實不是我吐槽,官方提供的一些組件樣式真的有點奇怪😭...雖然Angular官網 組件樣式這一文檔中已經說明了能夠用::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%);
}
複製代碼
效果大概就是這個樣子:
(錄頻並轉換GIF推薦使用 GifGam) 能夠看到,組件的樣式設計大多都是使用僞類: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....可讓咱們更有創造力吧應該說,也促使本身多去看別人的博客與源碼,最後再寫上一篇總結,我認爲這應該是一個正向的激勵💪,哈哈,全篇廢話不少,不過仍是要感謝小夥的閱讀🙂。