angular自定義組件-UI組件篇-switch組件

前言

前端框架多嗎?
多!
前端UI組件庫多嗎?
更多!
我們都知道,前端生態(tài)圈里提供了各色各樣的組件庫供我們選擇使用,大多數(shù)都能滿足開發(fā)者的需求,相信大家也都用過很多。但是實(shí)際上,據(jù)我了解到的,稍微大一些有自己產(chǎn)品的公司都會有一套自定義的UI組件庫,滿足自身復(fù)雜的需求與絢麗的效果。
博主目前所在的公司也有一套自己的產(chǎn)品,PC端所用的前端框架是Angular4.
Angular4其實(shí)也有它專門定制的前端組件庫PrimeNg.就像Vue.jsElement一樣.
那么按理在開發(fā)中我們已經(jīng)有了前端組件庫可以使用,為什么還要花那么多的精力和時間來重新設(shè)計(jì)UI組件呢?話不多說,先上幾張官方提供的UI組件圖:

offSwitch

onSwitch

確實(shí)不是我吐槽,官方提供的一些組件樣式真的有點(diǎn)奇怪??...雖然Angular官網(wǎng)組件樣式這一文檔中已經(jīng)說明了可以用::ng-deep來進(jìn)行對組件樣式的修改,但修改起來還是比較麻煩。有那時間,自己都已經(jīng)擼了一個了...
(項(xiàng)目中用的primengv4.2.2版本的,目前已經(jīng)迭代到了v6.1.5,所以現(xiàn)在官網(wǎng)上看到的inputSwitch組件會比這個好看點(diǎn))
emmm....為了追求用戶體驗(yàn)(呸,熟悉angular4的使用)所以博主決定利用閑暇之余自定義一些UI組件,以滿足我們產(chǎn)品"一些無禮的要求"。

一、確定組件存放的位置

一個項(xiàng)目中會有各種文件、文件夾,如何存放管理好這些文件真的很重要。不僅為自己提供了方便,也為后來的開發(fā)者提供方便。
所以我們在設(shè)計(jì)公用組件的時候也應(yīng)該把它們都?xì)w結(jié)在一起。
我習(xí)慣在項(xiàng)目中新建一個common文件夾,里面存放一些共用的compoentservice等等。

app/common/component

如上圖,可以看到common文件夾下導(dǎo)出的是一個名為shared的模塊。
shared模塊的創(chuàng)建過程:
(1)打開命令行(使用vscode編輯器的小伙可以直接使用Ctrl+` 快捷鍵打開終端,然后一路跳轉(zhuǎn)到common文件夾:

cd src\app\common

(2) 使用創(chuàng)建模塊的指令:

ng g m shared

其實(shí)很好理解:ngangular一貫的指令,ggenerate創(chuàng)建的縮寫,mmodule模塊的縮寫,后面接著你的模塊名。(后面創(chuàng)建組件也是這個原理)
創(chuàng)建的模塊實(shí)際上導(dǎo)出的是一個帶有@NgModule裝飾器的類而已,其中提供了我們自定義的公有組件component,公有服務(wù)service,以及管道pipe等等。

二、創(chuàng)建組件

由于我們要創(chuàng)建的是一個switch公用組件,所以在component文件夾下在創(chuàng)建一個文件夾general-control,之前都是直接堆積在component文件夾下的,近期發(fā)現(xiàn)堆得有點(diǎn)多了,所以又單獨(dú)創(chuàng)建了一個general-control文件夾來存放一些基礎(chǔ)的公用組件。
此時你需要打開命令行(使用vscode編輯器的小伙可以直接使用Ctrl+` 快捷鍵打開終端,然后一路跳轉(zhuǎn)到general-control文件夾:

cd src\app\common\component\general-control

在此目錄下執(zhí)行指令:

ng g c switch

上面指令的意思是創(chuàng)建一個名為switch的組件,原理和創(chuàng)建模塊時一樣。
可以看到現(xiàn)在的general-control文件夾下多出了一些東西:

switch文件夾

沒錯,就是我們使用指令創(chuàng)建的switch組件。
指令會自動幫你生成一個文件夾和4個文件。(基于TypeScript的語法,所以生成的js文件也就是ts)
很好理解,對應(yīng)的html文件編寫HTML代碼css文件編寫CSS代碼,ts文件編寫js代碼,至于spec.ts文件我們可以不用管它。
由于我在項(xiàng)目中使用的是sass,所以將switch.component.css這個文件的后綴名修改為scss(使用了less等其它擴(kuò)展語言的小伙同理),并在ts中對css的引用進(jìn)行修改:
scss

修改ts

使用上面的指令創(chuàng)建的組件是會被自動引用到shared這個模塊中的。
shared.module.ts:

import { SwitchComponent } from './component/general-control/switch/switch.component';//模塊中import引入組件

@NgModule({
declarations: [
  SwitchComponent  //模塊中聲明組件
  ...
  ]
})

上面?zhèn)z步是你在使用ng g c switch指令時自動幫你完成的,但若是你想在其它的模塊中使用這個switch組件,還得將其導(dǎo)出,導(dǎo)出的方式是將這個組件添加至shared.module.ts文件的exports中:

import { SwitchComponent } from './component/general-control/switch/switch.component';//模塊中import引入組件

@NgModule({
  declarations: [
    SwitchComponent  //模塊中聲明組件
    ...
    ],
  exports: [
    SwitchComponent  //模塊中導(dǎo)出組件
    ...
  ]
})

完成上面的步驟你就可以安心的來開發(fā)自己的組件了。

三、編寫switch組件

一番查找,發(fā)現(xiàn)網(wǎng)上也有很多自定義switch組件的文章和源碼,可能是大家都覺得原生的樣式不好看吧...
有使用input然后來進(jìn)行修改樣式的,也有用其它標(biāo)簽來自定義的。
博主這里找了一個最簡單方案,一個span標(biāo)簽搞定:

// switch.component.html
<span class="weui-switch" [ngClass]="currentClass" [ngStyle]="style" (click)="toggle()">
    
</span>

基礎(chǔ)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%);
  }

效果大概就是這個樣子:

自定義switch組件

(錄頻并轉(zhuǎn)換GIF推薦使用GifGam
可以看到,組件的樣式設(shè)計(jì)大多都是使用偽類:after:before來實(shí)現(xiàn)的,而開關(guān)的效果是通過點(diǎn)擊的時候添
加/移除classweui-switch-on來實(shí)現(xiàn)的。(講js的時候會講到)

由于我們創(chuàng)建的switch組件是需要在多處使用,并且要向外輸出一些值,所以在ts中我們首先要引入一下@Input、@Output裝飾器和EventEmitter。

import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';

并且定義一些基礎(chǔ)的變量

  @Input() style;//{ 'width': '40px' }//外部組件輸入的樣式對象
  @Input() isChecked: boolean = false;//開關(guān)是否打開
  @Input() disabled: boolean = false;//開關(guān)是否被禁用
  @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;//外部組件輸入進(jìn)來的:開關(guān)是否打開
  @Input() disabled: boolean = false;//開關(guān)是否被禁用
  @Output() change: EventEmitter<any> = new EventEmitter();
  _isSwitch: boolean = false;//switch組件本身的:開關(guān)是否打開
  currentClass = {} //class集合
  
  ngOnInit() {//初始化組件的生命周期
    
  }
  ngOnChanges() {//當(dāng)被綁定的輸入屬性的值發(fā)生變化時調(diào)用
    
  }
}

3.1 setIsSwitch()方法

組件中定義了倆個“開關(guān)是否打開”的變量isChecked_isSwitch
一個是外部組件傳遞進(jìn)來的默認(rèn)值,一個是 switch組件自身的值。
所以在組件進(jìn)行初始化和發(fā)生改變的時候我們應(yīng)該讓其統(tǒng)一:

  ngOnInit() {//初始化組件的生命周期
    this.setIsSwitch();
  }
  ngOnChanges() {//當(dāng)被綁定的輸入屬性的值發(fā)生變化時調(diào)用
    this.setIsSwitch();
  }
  setIsSwitch() {//設(shè)置_isSwitch
    this._isSwitch = this.isChecked;
  }

3.2 setStyle()方法

由于是自定義的組件,我們當(dāng)然是希望大小也可以自定義,所以我想要的效果是:
在調(diào)用組件的時候,輸入一個寬度width屬性,組件能夠自動調(diào)節(jié)尺寸。
因此我在設(shè)計(jì)的時候就定義了一個style變量
它是一個對象,可以允許開發(fā)者輸入任意的樣式,格式為{ 'width': '40px' }
同時為了減少輸入樣式的復(fù)雜度,我們還可以來編寫一個方法,讓組件能夠根據(jù)寬度來調(diào)節(jié)高度:

setStyle() {//設(shè)置樣式
    if (this.style) {
      if (this.style['width'] && !this.style['height']) {//若是輸入了寬度沒有輸入高度則自動計(jì)算
        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()方法是判斷有沒有寬度和高度,并將高度設(shè)置為0.55 * width(0.55為我找到的最合適的比例)

3.3 setClass()方法

完成了上面的步驟我們基本就完成了對組件樣式的初始化,但是,最重要的一步當(dāng)然是通過添加/移除一些類來進(jìn)行組件的交互:

 setClass() {//轉(zhuǎn)換switch時切換class
    this.currentClass = {
      'disabled': this.disabled,
      'bg_main bor_main weui-switch-on': this._isSwitch
    }
  }

對象currentClass存儲的是組件變動的類名,對象的鍵名為類名,值為一個布爾類型的變量(true / false)
通過布爾類型的變量來判斷添加還是移除這些類名。
第一個類disabled表示的是開關(guān)是否被禁用,也就是用戶只能查看開關(guān),并不能對其進(jìn)行操作,它受disabled變量控制。
第二個類為三個類名的合寫bg_main、bor_main、和weui-switch-on,他們受_isSwitch變量控制,
也就是開關(guān)打開的時候則添加這三個類。
前倆個類名是我在項(xiàng)目中使用的“皮膚類名”,因?yàn)榭蛻舻男枰覀儺a(chǎn)品有幾套不同的主題色,用戶可以進(jìn)行換膚功能來切換主題色,因此就有一些類名需要用來控制主題色。
如橘色主題:

.bg_main {
            background-color: #ff7920!important;
}
.bor_main {
            border-color: #ff7920!important;
}

當(dāng)然,你若是沒有主題色的話請忽略這倆個類。

上面的幾個方法我們都需要在組件初始化和變量發(fā)生改變的時候調(diào)用,所以可以整合到一個函數(shù)中:

  ngOnInit() {
    this.initComponent();
  }
  ngOnChanges() {
    this.initComponent();
  }
  initComponent() {
    this.setIsSwitch();
    this.setStyle();
    this.setClass();
  }

3.4 toggle()方法

光有樣式可沒用,我們還需要將組件和用戶的行為給結(jié)合在一起,因此給組件一個click事件來進(jìn)行交互,并編寫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;//開關(guān)是否打開
  @Input() disabled: boolean = false;//開關(guān)是否被禁用
  @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() {//設(shè)置樣式
    if (this.style) {
      if (this.style['width'] && !this.style['height']) {//若是輸入了寬度沒有輸入高度則自動計(jì)算
        let width = this.getWidth(this.style['width']);
        this.style['height'] = (width * 0.55) + 'px';
      }
    }
  }
  setClass() {//轉(zhuǎn)換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組件

完成了上面的部分,到了我們最激動的時候了,看看我們親手制作的組件有沒有用吧,哈哈。
首先,在使用其它組件的時候,我們要將其引入進(jìn)來,由于我們最開始是將switch組件引入到shared這個模塊中,并從這個模塊中導(dǎo)出的,所以想要在其它模塊中使用 switch組件就得先引入shared模塊。

4.1 引入shared模塊

本項(xiàng)目中有另一個模塊名為coursemanage,現(xiàn)在我將其作為父組件來引用一下switch組件
首先在模塊里引用:

//coursemanage.module.ts
import { NgModule } from '@angular/core';
import { SharedModule } from "./../common/shared.module";
@NgModule({
  imports: [
      SharedModule
  ]
})
export class CourseManageModule { }

引入了shared模塊就相當(dāng)于是引入那個那個模塊中的所有組件和方法。

4.2 使用switch組件

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組件的編寫和使用。
你也可以給組件設(shè)置另一個屬性disabled:

<!--course.component.html-->
<app-switch [isChecked]="dataStatus" [disable]="true" (change)="changeSwitch($event)"></app-switch>

后語

上述設(shè)計(jì)的switch組件應(yīng)該是UI組件中比較簡單的一種UI組件了,還有更多復(fù)雜的組件有待我們的開發(fā),通過自己設(shè)計(jì)UI組件,emmm....可以讓我們更有創(chuàng)造力吧應(yīng)該說,也促使自己多去看別人的博客與源碼,最后再寫上一篇總結(jié),我認(rèn)為這應(yīng)該是一個正向的激勵??,哈哈,全篇廢話很多,不過還是要感謝小伙的閱讀??。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容