乐趣区

关于vue.js:elementui源码学习之仿写一个elbutton

本篇文章记录仿写一个 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…

退出移动版