2
This article records and imitates a el-switch component details, which will help you better understand the specific working details of the corresponding components of Ele.me ui. This article is another article in the elementui source code learning and imitation series. I will continue to update and imitate other components when I am free. The source code is on github, you can pull it down, npm start to run, and comments are helpful for better understanding. The github warehouse address is as follows: https://github.com/shuirongshuifu/elementSrcCodeStudy

switch component thinking

component function

switch组件 generally means the switch state or the switch between two states, such as clicking to turn on the night mode of the website, or turn off the night mode. As shown in the figure below, the homepage of the Vue official website has such an operation function:

Vue official website link address: https://cn.vuejs.org/

Component structure

switch组件 The structure is relatively simple, mainly divided into two parts:

  1. switch组件切换小圆点按钮
  2. switch组件切换容器

Component implementation ideas

basic switch switch layout structure

switch组件的时候, switch组件切换容器 div去表示,那么switch组件切换小圆点按钮 div ? Actually no need.

  1. We can use 伪元素 to draw a first 切换小圆点按钮 (combined with positioning)
  2. Then you need to define a 标识布尔值 for changing 切换组件开启关闭状态
  3. When the state changes, go to change 切换小圆点按钮 on the left or on the right position (by class), that is, to realize the switching function
  4. Coupled with the transition effect, in this case, the switching (on and off) of switch组件 will be very smooth

On and off the description text function of switch组件 Notes

As shown below:

  1. Regarding the text on the left when opening, and the text on the right when closing, it can also be controlled by the elastic box style justifyContent:flex-start / flex-end; , of course dynamic padding should also be added, see the code for details
  2. If you add text to the inside of the switch box, you need to make the width of the background container dom of the switch box adaptive, that is, to control it according to the amount of content text, so it is necessary to mention width: fit-content;属性 (using the fit-content attribute, let The width adapts to the content of the text)
For details about fit-content, see the official documentation: https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content

Add hover effect to pseudo-elements

给伪元素加悬浮效果是先hover再::after(不要搞反了)

Correct spelling: .target:hover::after { background-color: red; }

Wrong spelling!!!: .target::after:hover { background-color: red; }

Here is an example code rendering, which can be used by copying and pasting, as follows:

 <!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>

Other things about the package mySwitch组件 can be clearly understood with the author's comments. This component is mainly for dynamic control of styles.

In addition, the components encapsulated by the author are not used for the verification of el-form . When the form verification is written later, it will be added and updated in the code repository on github.

Of course, the author's plan is slightly different from the official one. After all, the ideas are slightly different, and readers are also advised to try to imitate the package by themselves.

packaged components

renderings

The author's gif screen recording software is not very good. Do you have any good gif recording software to recommend ^_^

Copy and paste to use

use code

 <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>

package code

Referring to the notes, it is recommended to encapsulate the one suitable for your company's business 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>
Note true-value和false-value is the official v-model attribute that comes with it. In fact, it is not necessary here. You can see it by referring to the components of antd. See: https://cn.vuejs.org/guide/essentials/forms.html#checkbox-2

水冗水孚
1.1k 声望588 粉丝

每一个不曾起舞的日子,都是对生命的辜负