记录--elementui源码学习之仿写一个el-button

news2025/7/18 23:15:22

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

elementui源码学习之仿写一个el-button

本篇文章记录仿写一个el-button组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节。本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件。源码在github上,大家可以拉下来,npm start运行跑起来,结合注释有助于更好的理解

网站效果演示:ashuai.work:8888/#/myButton

GitHub仓库地址:github.com/shuirongshu…

什么是Button组件

按钮用于点击,一般是做事件的响应。

按钮封装效果图

1111.gif

按钮分类

  • 单一按钮
    • 默认按钮
    • 主题按钮(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>

本文转载于:

https://juejin.cn/post/7182113902539309112

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/395777.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity iOS 无服务器做一个排行榜 GameCenter

排行榜需求解决方案一(嗯目前只有一)UnityEngine.SocialPlatformsiOS GameCenterAppStoreConnect配置Unity 调用(如果使用GameCenter系统的面板&#xff0c;看到这里就可以了&#xff09;坑(需要获取数据做自定义面板的看这里)iOS代码Unity 代码吐槽需求 需求&#xff1a;接入…

某某游戏加密坐标分析

这个游戏里面坐标有很多种存放方式。 例如明文存放的DOUBLE&#xff0c;加密的各种类型。 我们不知道哪一个对于我们是有用的,哪一些只是辅助UI或则掉到LUA虚拟机坑里的数据。 那就根据作用大小来决定,一一尝试吧。 最好去找修改之后有效果的地址&#xff0c;当然只是本地&…

记一次影视cms黑盒CSRF-RCE

俗话说得好&#xff0c;思路才是最重要&#xff0c;本文章主要提供思路&#xff0c;各位师傅在挖掘漏洞的时候说不定也能碰到类似的点1.思路&#xff1a;当我们在找可以构建csrf的时候&#xff0c;多找找可以提交上传图片的&#xff0c;部分是可以自由构建url如图&#xff1a;漏…

Python数据分析案例20——我国家庭资产影响因素分析

本次案例较为简单&#xff0c;符合人文社科、经济学管理学等专业本科生适用。 本文的数据来源于中国家庭金融调查&#xff08;China Household Finance Survey&#xff0c;CHFS&#xff09;是西南财经大学中国家庭金融调查与研究中心&#xff08;下称中心&#xff09;在全国范围…

后端快速上手Vue+axios

文章目录前言vue基础1.el:挂载点2.data:数据对象vue常见指令vue生命周期axiosvueaxios前言 面向后端人员&#xff0c;旨在快速熟悉Vue框架&#xff0c;更详细的以后再总结 &#xff08;1&#xff09;Vue的特性&#xff1a; JavaScript框架简化Dom操作响应式数据驱动 &#…

JWT详细介绍使用

一、JWT介绍 JWT是JSON Web Token的缩写&#xff0c;即JSON Web令牌&#xff0c;是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息&#xff0c;以便于从资源服务…

【经验分享】电路板上电就挂?新手工程师该怎么检查PCB?

小伙伴们有没有经历过辛辛苦苦&#xff0c;加班加点设计的PCB&#xff0c;终于搞定下单制板。接下来焦急并且忐忑地等待PCB板到货&#xff0c;焊接&#xff0c;验证&#xff0c;一上电&#xff0c;结果直接挂了... 连忙赶紧排查&#xff0c;找问题。最终发现&#xff0c;是打过…

学习笔记:基于SpringBoot的牛客网社区项目实现(二)之Spring MVC入门

1.1 函数的返回值为空&#xff0c;因为可以使用response对象向浏览器返回数据。声明了request对象和response对象&#xff0c;dispatcherservlet自动将这两个对象传入 RequestMapping("/http")public void http(HttpServletRequest request, HttpServletResponse re…

ReentranLock(可重入锁)

一、ReentranLock ReentranLock属于JUC并发工具包下的类&#xff0c;相当于 synchronized具备如下特点 ● 可中断 ● 可以设置超时时间 ● 可以设置为公平锁&#xff08;防止线程出现饥饿的情况&#xff09; ● 支持多个条件变量 与 synchronized一样&#xff0c;都支持可重…

浅析 SplitChunksPlugin 及代码分割的意义

本文作者为 360 奇舞团前端开发工程师起因有同事分享webpack的代码分割&#xff0c;其中提到了SplitChunksPlugin&#xff0c;对于文档上的描述大家有着不一样的理解&#xff0c;所以打算探究一下。Q&#xff1a;什么是 SplitChunksPlugin&#xff1f;SplitChunksPlugin 是用来…

Python所有方向的入门和进阶路线,20年老师傅告诉你方法

干了20多年程序员&#xff0c;对于Python研究一直没停过&#xff0c;这几天把我自己对Python的认知和经验&#xff0c;再结合很多招聘网站上的技术要求&#xff0c;整理出了Python所有方向的学习路线图&#xff0c;基本上各个方向应该学什么&#xff0c;都在上面了&#xff0c;…

macOS 13.3 Beta 3 (22E5236f)发布

系统介绍3 月 8 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 13.3 开发者预览版 Beta 3 更新&#xff08;内部版本号&#xff1a;22E5236f&#xff09;&#xff0c;本次更新距离上次发布隔了 7 天。macOS Ventura 带来了台前调度、连续互通相机、FaceTime 通话接力…

文件预览kkFileView安装及使用

1 前言网页端一般会遇到各种文件&#xff0c;比如&#xff1a;txt、doc、docx、pdf、xml、xls、xlsx、ppt、pptx、zip、png、jpg等等。有时候我们不想要把文件下载下来&#xff0c;而是想在线打开文件预览 &#xff0c;这个时候如果每一种格式都需要我们去写代码造轮子去实现预…

k8s pod调度总结

在Kubernetes平台上&#xff0c;我们很少会直接创建一个Pod&#xff0c;在大多数情况下会通过控制器完成对一组Pod副本的创建、调度 及全生命周期的自动控制任务&#xff0c;如&#xff1a;RC、Deployment、DaemonSet、Job 等。本文主要举例常见的Pod调度。1全自动调度功能&…

第二章:基础语法

第二章&#xff1a;基础语法 2.1&#xff1a;关键字和保留字 关键字 定义&#xff1a;被Java语言赋予了特殊含义&#xff0c;用做专门用途的字符串(单词) 特点&#xff1a;关键字中所有字母都为小写 分类&#xff1a; 用于定义数据类型的关键字 class、interface、enum、byt…

算法设计与分析——递归与分治策略——全排列Perm函数

删除线格式 [toc] 问题描述 现给出m个不同的数字&#xff0c;在n个位置上&#xff0c;对齐进行全排列。使用编程实现数学中全排列输出最终计算结果并将所有的排列打印出来。 思路分析 常规的递归方式进行解决即可&#xff0c;递归的终点是根据题目要求进行实现。共有两个参…

第一次运行vue遇到的问题

1.vue无法识别https://blog.csdn.net/weixin_61634408/article/details/1265897982.yarn serve问题https://blog.csdn.net/fangxuan1509/article/details/104711690/3.关闭控制台报错检查&#xff08;每次vue-rounter必须用&#xff09;vue.config,js,的module.exports 中添加l…

【Linux】sudo指令

在本期博客正式开始之前&#xff0c;我们先来解决一个历史遗留问题&#xff1a;sodu指令怎么用不了&#xff1f;sudo指令&#x1f4cc;sudo是linux下常用的允许普通用户使用超级用户权限的工具&#xff0c;允许系统管理员让普通用户执行一些或者全部的root命令&#x1f4cb;但是…

【预告】ORACLE Unifier v22.12 虚拟机发布

引言 离ORACLE Primavera Unifier 最新系统 v22.12已过去了3个多月&#xff0c;应盆友需要&#xff0c;也为方便大家体验&#xff0c;我近日将构建最新的Unifier的虚拟环境&#xff0c;届时将分享给大家&#xff0c;最终可通过VMWare vsphere (esxi) / workstation 或Oracle …

【Spring6】| Bean的四种获取方式(实例化)

目录 一&#xff1a;Bean的实例化方式 1. 通过构造方法实例化 2. 通过简单工厂模式实例化 3. 通过factory-bean实例化 4. 通过FactoryBean接口实例化 5. BeanFactory和FactoryBean的区别&#xff08;面试题&#xff09; 6. 使用FactoryBean注入自定义Date 一&#xff1a…