elementui原始碼學習之仿寫一個el-switch

語言: CN / TW / HK

本篇文章記錄仿寫一個 el-switch 元件細節,從而有助於大家更好理解餓了麼ui對應元件具體工作細節。本文是elementui原始碼學習仿寫系列的又一篇文章,後續空閒了會不斷更新並仿寫其他元件。原始碼在github上,大家可以拉下來,npm start執行跑起來,結合註釋有助於更好的理解。github倉庫地址如下: http://github.com/shuirongsh...

switch元件思考

元件功能作用

switch元件 一般是表示開關狀態或者兩種狀態之間的切換,如點選開啟網站的夜間模式,或關閉夜間模式。如下圖vue官網首頁就有這樣的操作功能:

vue官網連結地址: http://cn.vuejs.org/

元件的結構

switch元件 的結構還是比較簡單的,主要分為兩部分:

switch元件切換小圓點按鈕
switch元件切換容器

元件的實現思路

基本的 switch 切換佈局結構

在實現 switch元件 的時候, switch元件切換容器 可以直接畫一個 div 去表示,那麼 switch元件切換小圓點按鈕 我們也需要畫一個 div 嗎?其實不用的。

  1. 我們可以使用 偽元素 先畫出一個 切換小圓點按鈕 (結合定位)
  2. 然後需要定義一個 標識布林值 ,用於更改 切換元件開啟關閉狀態
  3. 當狀態變化的時候,去更改 切換小圓點按鈕 在左側或在右側的位置(通過class),即實現了切換功能
  4. 再加上過渡效果,這樣的話, switch元件 的切換(開啟關閉)就會很絲滑了

開啟關閉 switch元件 的說明文字功能注意事項

如下圖:

  1. 關於開啟時候文字在左側,關閉時候文字在右側,也開始可以通過彈性盒樣式控制 justifyContent:flex-start / flex-end; ,當然動態 padding 也要加上,詳情見程式碼
  2. 若將文字加入切換框內部,那麼就需要讓切換框背景容器dom的寬度自適應,即根據內容文字的多少來控制,所以要提到 width: fit-content;屬性 (使用fit-content屬性,讓寬度隨著內容文字多少自適應)

關於 fit-content 詳情見官方文件: http://developer.mozilla.org...

給偽元素加上hover效果的寫法

給偽元素加懸浮效果是先hover再::after(不要搞反了)

正確寫法: .target:hover::after { background-color: red; }

錯誤寫法!!!: .target::after:hover { background-color: red; }

這裡舉一個例子程式碼效果圖,複製貼上即可使用,如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            padding: 120px;
        }

        .target {
            display: inline-block;
            width: 60px;
            height: 18px;
            background-color: #c4c4c4;
            border-radius: 10px;
            cursor: pointer;
            transition: all 0.3s;
            position: relative;
        }

        /* 使用偽元素畫一個小圓點 */
        .target::after {
            content: "";
            position: absolute;
            top: -4px;
            left: -2px;
            border-radius: 50%;
            width: 24px;
            height: 24px;
            border: 1px solid #e9e9e9;
            box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.3);
            background-color: #fff;
            transition: all 0.3s;
        }

        /* 給自己加懸浮效果直接寫即可 */
        .target:hover {
            background-color: green;
        }

        /* 給偽元素加懸浮效果是先hover再::after(不要搞反了) */
        .target:hover::after {
            background-color: red;
        }
    </style>
</head>

<body>
    <div class="target"></div>
</body>

</html>

關於封裝的 mySwitch元件 的其他的東西,結合筆者的註釋,就可以清晰的理解了。這個元件主要還是樣式的動態控制。

另,筆者封裝的元件暫不搭配 el-form 的校驗使用,後續寫到表單校驗時,會補上並更新github上的程式碼倉庫中

當然部分寫法效果,筆者的方案和官方的還是略有不同,畢竟思路略有不同,也建議讀者自己嘗試仿寫封裝哦

封裝的元件

效果圖

筆者的gif錄屏軟體不太好,道友朋友們有沒有不錯的gif錄製軟體推薦一下 ^_^

複製貼上即可使用哦

使用程式碼

<template>
  <div>
    <my-divider lineType="dotted" content-position="left">普通使用</my-divider>
    <my-switch @change="change" v-model="flag1"></my-switch>
    <my-switch v-model="flag2"></my-switch>
    <my-divider lineType="dotted" content-position="left"
      >開啟關閉文字</my-divider
    >
    <my-switch v-model="flag3" openText="開啟啦開啟啦" closeText="關閉了"></my-switch>
    <my-switch v-model="flag3" openText="ON" closeText="OFF"></my-switch>
    <my-switch v-model="flag3" openText="✔" closeText="✘"></my-switch>
    <my-divider lineType="dotted" content-position="left"
      >自定義開啟關閉背景色</my-divider
    >
    <my-switch
      v-model="flag4"
      active-color="#19be6b"
      inactive-color="#ed4014"
    ></my-switch>
    <my-divider lineType="dotted" content-position="left">禁用</my-divider>
    <my-switch v-model="flag5" disabled></my-switch>
    <my-switch v-model="flag6" disabled></my-switch>
    <br />
    <my-divider lineType="dotted" content-position="left"
      >small切換框</my-divider
    >
    <my-switch
      v-model="flag7"
      active-color="#006CFF"
      inactive-color="#DD6DA6"
      openText="small"
      closeText="switch"
      size="small"
    ></my-switch>
    <my-divider lineType="dotted" content-position="left">big切換框</my-divider>
    <my-switch
      v-model="flag8"
      active-color="#2F2F2F"
      inactive-color="#ddd"
      openText="☾"
      closeText="☼"
      size="big"
    ></my-switch>
  </div>
</template>

<script>
export default {
  data() {
    return {
      flag1: true,
      flag2: false,
      flag3: true,
      flag4: true,
      flag5: false,
      flag6: true,
      flag7: true,
      flag8: true,
    };
  },
  methods: {
    change(val) {
      console.log("切換後的狀態", val);
    },
  },
};
</script>

封裝程式碼

參考註釋,建議自己封裝適合自己公司業務的 switch元件

<template>
  <div
    class="mySwitchWrap"
    :class="[disabled ? 'disabledSwitch' : '', size]"
    @click="changeStatus"
  >
    <!-- input標籤 -->
    <input
      class="switchInput"
      type="checkbox"
      @change="changeStatus"
      ref="input"
      :true-value="activeValue"
      :false-value="inactiveValue"
      :disabled="disabled"
      @keydown.enter="changeStatus"
    />
    <!-- 主要內容 -->
    <span
      :class="[
        'switchCentre',
        'circleDotLeft',
        isOpen ? 'changeCircleDotRight' : '',
      ]"
      :style="{
        background: computedBackground,
        borderColor: computedBackground,
      }"
    >
      <span
        class="text"
        :style="{
          justifyContent: isOpen ? 'flex-start' : 'flex-end',
          padding: isOpen ? '0 28px 0 8px' : '0 8px 0 28px',
        }"
        >{{ isOpen ? openText : closeText }}</span
      >
    </span>
  </div>
</template>

<script>
export default {
  name: "mySwitch",
  props: {
    openText: String,
    closeText: String,
    // v-model搭配value接收資料,this.$emit("input", val)更新資料
    value: {
      type: Boolean,
      default: false, // 預設false
    },
    // 是否禁用,預設不禁用
    disabled: {
      type: Boolean,
      default: false,
    },
    // switch開啟時為true
    activeValue: {
      type: Boolean,
      default: true,
    },
    // switch關閉時為false
    inactiveValue: {
      type: Boolean,
      default: false,
    },
    // 自定義switch開啟時背景色
    activeColor: {
      type: String,
      default: "",
    },
    // 自定義switch關閉時背景色
    inactiveColor: {
      type: String,
      default: "",
    },
    // switch切換框的大小
    size: {
      type: String,
      default: "",
    },
  },
  computed: {
    // 是否開啟切換框取決於外層傳遞的v-model的值是否為true
    isOpen() {
      return this.value === this.activeValue;
    },
    computedBackground() {
      // 若傳遞了啟用顏色和未啟用顏色,就根據是否開啟狀態使用傳遞的顏色
      if ((this.activeColor.length > 0) & (this.inactiveColor.length > 0)) {
        return this.isOpen ? this.activeColor : this.inactiveColor;
      }
      // 沒傳遞就根據開啟使用預設的背景色
      else {
        return this.isOpen ? "#409EFF" : "#C0CCDA";
      }
    },
  },
  methods: {
    changeStatus() {
      // 禁用情況下,不做狀態更改切換
      if (this.disabled) {
        return;
      }
      // 首先看是否開啟,若開啟,就傳遞不開啟;若不開啟,就傳遞開啟(因為狀態切換,取反)
      const val = this.isOpen ? this.inactiveValue : this.activeValue;
      this.$emit("input", val); // 更新外層v-model繫結的值
      this.$emit("change", val); // 丟擲一個change事件以供使用者使用
    },
  },
};
</script>

<style scoped lang="less">
.mySwitchWrap {
  display: inline-block;
  cursor: pointer;
  font-size: 14px;
  margin: 2px;
  /* 將input標籤隱藏起來,寬高都為0,透明度也為0,看不到 */
  .switchInput {
    position: absolute;
    width: 0;
    height: 0;
    opacity: 0;
    margin: 0;
  }
  .switchCentre {
    display: inline-block;
    width: auto;
    height: 20px;
    color: #fff;
    background-color: #c4c4c4;
    border: 1px solid;
    outline: 0;
    border-radius: 10px;
    box-sizing: border-box;
    transition: all 0.3s; // 加上過渡效果
    position: relative;
    .text {
      min-width: 54px; // 設定最小寬度
      width: fit-content; // 使用fit-content屬性,讓寬度隨著內容多少自適應
      height: 100%;
      font-size: 12px;
      display: flex;
      // justify-content: justifyContent; // 注意,這裡也是通過:style控制文字靠左還是靠右
      align-items: center;
      transition: all 0.3s; // 加上過渡效果
    }
  }
  // 預設小圓點在左側的(使用偽元素建立一個小圓點)
  .circleDotLeft::after {
    content: "";
    position: absolute;
    top: -4px;
    left: -2px;
    border-radius: 100%;
    width: 24px;
    height: 24px;
    border: 1px solid #e9e9e9;
    box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.3); // 原來小圓點有一點陰影
    background-color: #fff;
    transition: all 0.3s; // 加上過渡效果
  }
  // 當switch開啟時,加上類名~通過更改定位left值控制圓點在右側
  .changeCircleDotRight::after {
    left: 100%;
    margin-left: -24px;
  }
  // 懸浮加重小圓點陰影
  .circleDotLeft:hover::after {
    box-shadow: 0 1px 18px 0 rgba(0, 0, 0, 0.5);
  }
}
// 除了cursor樣式的not-allowed還要搭配js判斷才禁用到位
.disabledSwitch {
  cursor: not-allowed;
  opacity: 0.48;
}
// 禁用情況下,保持小圓點原有陰影
.disabledSwitch .circleDotLeft:hover::after {
  box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.3);
}
// 小型switch元件做一個縮放
.small {
  zoom: 0.8;
}
// 大型switch元件做一個縮放
.big {
  zoom: 1.6;
}
</style>

注意 true-value和false-value 是官方自帶的搭配v-model屬性,其實這裡不用也行,大家參考一下antd的元件便可明瞭。詳見: http://cn.vuejs.org/guide/es...