本篇文章記錄仿寫一個
el-button
元件細節,從而有助於大家更好理解餓了麼ui對應元件具體工作細節。本文是elementui原始碼學習仿寫系列的又一篇文章,後續空閒了會不斷更新並仿寫其他元件。原始碼在github上,大家可以拉下來,npm start執行跑起來,結合註釋有助於更好的理解網站效果演示:http://ashuai.work:8888/#/myB...
GitHub倉庫地址:https://github.com/shuirongsh...
什麼是Button元件
按鈕用於點選,一般是做事件的響應。
按鈕封裝效果圖
按鈕分類
單一按鈕
- 預設按鈕
- 主題按鈕(primary、success、warning、error)
- 按鈕大小(small、middle、big)
- 按鈕禁用(disabled)
- 按鈕載入(loading)
- 按鈕的圖示位置(預設圖示在按鈕文字左側)
- 圖示按鈕(沒有按鈕文字)
- 單一文字按鈕
- 按鈕組(按鈕組中有多個按鈕)
預設按鈕
預設按鈕很簡單,只是寫一個最普通的樣式即可
<button :class="[ 'myButton' ]" />
.myButton {
display: inline-flex;
align-items: center;
justify-content: center;
white-space: nowrap;
box-sizing: border-box;
padding: 12px 16px;
background-color: rgba(0, 0, 0, 0.1);
color: #222;
border: none;
cursor: pointer;
user-select: none; // 不讓選中文字
transition: all 0.3s;
font-size: 14px;
}
// 懸浮效果
.myButton:hover {
background-color: rgba(0, 0, 0, 0.2);
}
// 按中效果
.myButton:active {
background-color: rgba(0, 0, 0, 0.3);
}
筆者這裡是將懸浮的效果和按中的效果,設定背景色越來越深。這樣的話,看著效果比較明顯
主題按鈕
所謂按鈕的主題,就是新增不同的類名,比如primary
主題的按鈕,就加上.primary
類名、success
主題的按鈕,就加上.success
類名。然後使用動態class
去新增即可(這裡使用動態class的陣列用法)。如:
<button :class="[ 'myButton', type ]" />
變數type
的值源自於使用按鈕元件時,傳遞進來的type
引數
const typeArr = [
"",
"primary",
"success",
"warning",
"error",
"text",
"dangerText",
];
props:{
type: { // 按鈕主題型別
type: String,
validator(val) {
return typeArr.includes(val); // 這裡可以加一個校驗函式,其實不加也行
},
},
}
然後給不同type值加上對應的樣式即可。如下:
// primary樣式
.primary {
background-color: #1867c0;
color: #fff;
}
.primary:hover {
background-color: #0854ac;
}
.primary:active {
background-color: #033d7f;
}
// success樣式
.success {
background-color: #19be6b;
color: #fff;
}
.success:hover {
background-color: #0ea459;
}
.success:active {
background-color: #008140;
}
// warning樣式
.warning {
background-color: #ffc163;
color: #fff;
}
.warning:hover {
background-color: #db952d;
}
.warning:active {
background-color: #b97b1d;
}
// 等等type值樣式...
按鈕大小
按鈕大小可以使用padding
值的大小去控制,也可以直接使用zoom
縮放做控制
這裡使用動態style
搭配計算屬性的方式去控制,如下程式碼:
// 不同的大小指定不同的縮放程度
const sizeObj = {
small: 0.85,
middle: 1,
big: 1.2,
};
props:{ size: String }
<button :style="styleCal" />
computed: {
styleCal() {
return {
zoom: sizeObj[this.size] // zoom縮放的值大小取決於傳遞進來的size值
}
}
}
按鈕禁用
按鈕禁用disable
沒啥好說的,主要是要注意loading
的時候,也要禁用掉,loading
載入的時候,不允許使用者再點選。
<button :disabled="disabled || loading" />
props:{
loading:Boolean
}
這裡注意一下,按鈕禁用的樣式也是透過動態class加上的,請往下看
按鈕載入
注意載入時樣式和載入按鈕圖示出來的時候,將其他的圖示給隱藏起來。(同一時刻,只能有一個按鈕圖示,這樣保證按鈕載入時簡潔一些)
<button
:class="[
'myButton', // 預設樣式
disabled ? 'disabledBtn' : '', // 動態加上禁用按鈕樣式
loading ? 'loadingBtn' : '', // 動態加上loading載入中按鈕樣式
type, // 主題樣式
]"
:disabled="disabled || loading" // 禁用時禁用,載入時也禁用
>
<i class="el-icon-loading" v-if="loading"></i>
<!-- 使用傳進來的圖示,透過動態style控制圖示和文字見的間隔,同一時刻下,
只能有一個圖示出現,所以有loading圖示了,就不能有別的圖示了 -->
<i :class="icon" :style="styleGap" v-if="icon && !loading"></i>
<slot></slot>
</button>
按鈕的圖示位置
預設從左往右排列(圖示在左側、文字在右側),這裡我們可以使用彈性盒的方向flexDirection
屬性,來控制從左往右還是從右往左排列
<button :style="styleCal"/>
styleCal() {
// 控制縮放和指定預設圓角以及設定圖示在文字左側還是右側
let styleObj = {
zoom: sizeObj[this.size],
borderRadius: "5px",
flexDirection: this.rightIcon ? "row-reverse" : "row",
};
return styleObj;
},
圖示按鈕和單一文字按鈕
這兩個也很簡單,
- 圖示按鈕注意加圓角的時機
- 單一文字按鈕的樣式要預留設定一份即可
然後動態控制一下即可
按鈕組
按鈕組注意事項:
- 首先將所有的按鈕的圓角全部去掉(這樣的話,所有的按鈕都是方方正正的按鈕了)
- 然後單獨給第一個按鈕
:first-of-type
的左上角和左下角的圓角設定一下 - 然後再給最後一個按鈕
last-of-type
的右上角和右下角的圓角設定一下 - 最後,按鈕組之間需要有間隔,這裡使用
border-right
做分割線 - 最最後,再把最後一個按鈕的右邊框去掉即可,如下css程式碼
// 附上按鈕組樣式
.myButtonGroup > .myButton {
border-radius: unset !important; // 給所有的按鈕都去掉圓角
border-right: 1px solid #fff; // 給按鈕加上分隔線條
}
// 第一個按鈕左側圓角
.myButtonGroup > .myButton:first-of-type {
border-top-left-radius: 5px !important;
border-bottom-left-radius: 5px !important;
}
// 最後一個按鈕的右側圓角
.myButtonGroup > .myButton:last-of-type {
border-top-right-radius: 5px !important;
border-bottom-right-radius: 5px !important;
border-right: none; // 同時,清除最後一個按鈕的右側邊框
}
程式碼
複製貼上即可使用,如果道友覺得程式碼幫忙到了您,歡迎給我們github倉庫一個star哈?
myButton元件
<template>
<button
:style="styleCal"
:class="[
'myButton',
disabled ? 'disabledBtn' : '',
loading ? 'loadingBtn' : '',
type,
]"
:disabled="disabled || loading"
@click="clickButton"
>
<i class="el-icon-loading iii" v-if="loading"></i>
<!-- 使用傳進來的圖示,透過動態style控制圖示和文字見的間隔,同一時刻下,
只能有一個圖示出現,所以有loading圖示了,就不能有別的圖示了 -->
<i :class="icon" :style="styleGap" v-if="icon && !loading"></i>
<!-- 普通插槽有東西才去渲染 -->
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
<script>
// 型別校驗
const typeArr = [
"",
"primary",
"success",
"warning",
"error",
"text",
"dangerText",
];
const sizeArr = ["", "small", "middle", "big"]; // 大小檢驗
const sizeObj = {
// 不同的大小指定不同的縮放程度
small: 0.85,
middle: 1,
big: 1.2,
};
export default {
name: "myButton",
props: {
disabled: Boolean,
loading: Boolean, // loading時,不可繼續點選(繼續點選不生效)
rightIcon: Boolean, // 透過彈性盒的方向控制圖示的位置
type: {
type: String,
validator(val) {
return typeArr.includes(val);
},
},
size: {
type: String,
validator(val) {
return sizeArr.includes(val);
},
},
icon: String,
},
computed: {
styleCal() {
// 控制縮放和指定預設圓角以及設定圖示在文字左側還是右側
let styleObj = {
zoom: sizeObj[this.size],
borderRadius: "5px",
flexDirection: this.rightIcon ? "row-reverse" : "row",
};
// 當有圖示,且沒有文字的時候(或預設插槽沒傳),就讓按鈕變成圓形按鈕
if ((this.icon && !this.$slots.default) || !this.$slots.default[0].text) {
styleObj["borderRadius"] = "50%";
styleObj["padding"] = "12px";
}
return styleObj;
},
styleGap() {
// 有圖示,有文字,圖示在左側
if (
(this.icon && !this.$slots.default) ||
(this.$slots.default[0].text && !this.rightIcon)
) {
return {
paddingRight: "1px",
};
}
// 有圖示,有文字,圖示在右側
if (
(this.icon && !this.$slots.default) ||
(this.$slots.default[0].text && this.rightIcon)
) {
return {
paddingLeft: "1px",
};
}
},
},
methods: {
clickButton(e) {
if (this.disabled) return;
this.$emit("click", e); // 傳出去,便於使用
},
},
};
</script>
<style lang='less' scoped>
/* 關於按鈕的樣式即寫好幾套樣式,然後透過型別等各種引數去控制樣式,最終實現對應效果 */
// 基礎樣式
.myButton {
display: inline-flex;
align-items: center;
justify-content: center;
white-space: nowrap;
box-sizing: border-box;
padding: 12px 16px;
background-color: rgba(0, 0, 0, 0.1);
color: #222;
border: none;
cursor: pointer;
user-select: none;
transition: all 0.3s;
font-size: 14px;
.iii {
margin-right: 4px;
}
}
.myButton:hover {
background-color: rgba(0, 0, 0, 0.2);
}
.myButton:active {
background-color: rgba(0, 0, 0, 0.3);
}
// primary樣式
.primary {
background-color: #1867c0;
color: #fff;
}
.primary:hover {
background-color: #0854ac;
}
.primary:active {
background-color: #033d7f;
}
// success樣式
.success {
background-color: #19be6b;
color: #fff;
}
.success:hover {
background-color: #0ea459;
}
.success:active {
background-color: #008140;
}
// warning樣式
.warning {
background-color: #ffc163;
color: #fff;
}
.warning:hover {
background-color: #db952d;
}
.warning:active {
background-color: #b97b1d;
}
// error樣式
.error {
background-color: #ff5252;
color: #fff;
}
.error:hover {
background-color: #fd3030;
}
.error:active {
background-color: #d50000;
}
// text樣式
.text {
background-color: unset;
color: #409eff;
padding: 2px 4px;
}
.text:hover {
background-color: unset;
opacity: 0.9;
}
.text:active {
background-color: unset;
opacity: 1;
color: #1a7ada;
}
// dangerText樣式
.dangerText {
background-color: unset;
color: #ff5252;
padding: 2px 4px;
}
.dangerText:hover {
background-color: unset;
opacity: 0.9;
}
.dangerText:active {
background-color: unset;
opacity: 1;
color: #d50000;
}
// 載入按鈕樣式
.loadingBtn {
opacity: 0.6;
pointer-events: none; // 值為none就沒有hover和active效果了
}
// disabled樣式(注意樣式的順序)
.disabledBtn {
background-color: rgba(0, 0, 0, 0.12);
color: #bbb;
}
.disabledBtn:hover {
opacity: 1;
cursor: not-allowed;
background-color: rgba(0, 0, 0, 0.12);
}
.disabledBtn:active {
color: #bbb;
opacity: 1;
background-color: rgba(0, 0, 0, 0.12);
}
// 附上按鈕組樣式
.myButtonGroup > .myButton {
border-radius: unset !important;
border-right: 1px solid #fff;
}
.myButtonGroup > .myButton:first-of-type {
border-top-left-radius: 5px !important;
border-bottom-left-radius: 5px !important;
}
.myButtonGroup > .myButton:last-of-type {
border-top-right-radius: 5px !important;
border-bottom-right-radius: 5px !important;
border-right: none;
}
</style>
myButtonGroup元件
<template>
<div class="myButtonGroup">
<slot></slot>
</div>
</template>
<script>
export default {
name: "myButtonGroup",
};
</script>
<style>
.myButtonGroup {
display: inline-flex !important;
align-items: center;
}
</style>
使用的時候
<template>
<div>
<h5>單個按鈕</h5>
<br />
<button @click="clickLoad">載入切換</button>
<div class="btnBox">
<span class="btn" v-for="(item, index) of btnArr">
<my-button
style="margin-right: 16px"
:key="index"
:type="item.type"
:size="item.size"
:disabled="item.disabled"
:loading="item.loading"
:icon="item.icon"
:rightIcon="item.rightIcon"
@click="
(e) => {
clickBtn(item, e);
}
"
>{{ item.name }}</my-button
>
</span>
</div>
<br />
<h5>按鈕組</h5>
<br />
<my-button-group>
<my-button type="success" icon="el-icon-arrow-left">上一頁</my-button>
<my-button type="success" icon="el-icon-arrow-right" :rightIcon="true"
>下一頁</my-button
>
</my-button-group>
<br />
<br />
<my-button-group>
<my-button type="primary" icon="el-icon-user"></my-button>
<my-button type="primary" icon="el-icon-view"></my-button>
<my-button type="primary" icon="el-icon-star-off"></my-button>
<my-button type="primary" icon="el-icon-chat-dot-square"></my-button>
<my-button type="primary" icon="el-icon-share"></my-button>
</my-button-group>
</div>
</template>
<script>
export default {
name: "myButtonName",
data() {
return {
loadingF: false,
btnArr: [
{
type: "",
name: "預設按鈕",
},
{
type: "primary",
name: "primary",
},
{
type: "success",
name: "success",
},
{
type: "warning",
name: "warning",
},
{
type: "error",
name: "error",
},
{
type: "primary",
name: "size=small",
size: "small",
},
{
type: "primary",
name: "size=middle",
size: "middle",
},
{
type: "primary",
name: "size=big",
size: "big",
},
{
type: "success", // 不管type什麼型別,只要禁用全部置灰
name: "disabled",
disabled: true,
},
{
type: "primary",
name: "等待載入",
loading: false,
},
{
type: "success",
name: "等待載入",
loading: false,
},
{
type: "success",
name: "icon",
icon: "el-icon-star-on",
},
{
type: "success",
name: "icon",
icon: "el-icon-star-on",
rightIcon: true,
},
{
type: "success",
name: "",
icon: "el-icon-edit",
},
{
type: "error",
name: "",
icon: "el-icon-delete",
},
{
type: "text",
name: "純text按鈕",
// loading: true,
},
{
type: "dangerText",
name: "dangerText按鈕",
icon: "el-icon-delete-solid",
},
{
type: "text",
name: "text禁用",
disabled: true,
},
],
};
},
methods: {
clickLoad() {
let lebel = this.btnArr[9].name;
let newItem9 = {
type: "primary",
name: lebel == "等待載入" ? "載入中" : "等待載入",
loading: lebel == "等待載入" ? true : false,
};
this.$set(this.btnArr, 9, newItem9);
let newItem10 = {
type: "success",
name: lebel == "等待載入" ? "載入中" : "等待載入",
loading: lebel == "等待載入" ? true : false,
};
this.$set(this.btnArr, 10, newItem10);
},
// 注意這種寫法,可接收多個引數
clickBtn(item, e) {
console.log("clickBtn", item, e);
},
},
};
</script>
<style>
.btnBox {
width: 100%;
box-sizing: border-box;
padding: 24px 0;
display: flex;
align-items: flex-end;
flex-wrap: wrap;
}
.btn {
margin-bottom: 24px;
}
</style>
A good memory is better than a bad pen. Write it down...