Vue复习姿势系列之UI组件——按钮(Button)

6,722 阅读2分钟

介绍

最常见的操作按钮,button是浏览器自带的操作按钮,但为了统一样式,我们会抹平浏览器之间的样式差距,顺便增加点新的功能。

想要实现的功能

功能介绍
typeprimary(主色) / success(成功) / warning(警告) / danger(危险) / info(信息) / text(文本)
disabled禁止使用
size4种尺寸,large(超大),medium(常规),small(小),mini(迷你)
icon可设置图标
round是否圆角按钮
circle是否圆形按钮
loading加载中状态

功能实现

1. 基础功能

src/packages 目录下新建button文件夹,文件夹内创建button.vueindex.js
src/styles 目录下心新建button.scss,并在src/styles/index.scss中引入

// button.vue
<template>
  <button class="my-button my-button-primary">
    <slot>按钮</slot>
  </button>
</template>

<script>

export default {
  name: 'Button',
  data () {
    return {

    }
  },
}
</script>
// button/index.js
import Button from './button.vue'
export default Button
// styles/button.scss
@charset "UTF-8";
@import "common/var";
@import "mixins/mixins";

@include b(button) {
  height: 36px;
  font-size: $--font-size-medium;
  color: #fff;
  border-radius: 4px;
  outline: none;
  border: 1px solid transparent;
  padding: 0 10px;
  cursor: pointer;
  &-primary {
    background-color: $--color-primary;
    // focus,hover的时,背景色变浅
    &:focus,
    &:hover {
      background-color: rgba($--color-primary, 0.7);
    }
    &:active {
      // active时,背景色变浅,且与focus,hover做区分
      background-color: rgba($--color-primary, 0.9);
    }
  }
}
// styles/index.scss
@import "button";

image.png

2. 类型type

Button的类型区分主要是通过背景色区分的。因此只要根据type设置class即可。
总共设计了6种类型,primary(默认),info,success,warning,error,text

// button.vue
<template>
  <button class="my-button" :class="{[`my-button-${type}`]: true}">
    <slot>按钮</slot>
  </button>
</template>

<script>
// 工具函数,用于判断传入的值是否符合条件
import { oneOf } from '../../utils/assist'

export default {
  name: 'Button',
  data () {
    return {

    }
  },
  props: {
    type: {
      validator(value) {
        return oneOf(value, [
          'primary',
          'info',
          'success',
          'warning',
          'text',
          'error',
        ])
      },
      type: String,
      default: 'primary',
    },
  },
}
</script>

然后是不同type对应的样式

// button.scss --- 省略部分代码 ---
&-info {
    background-color: $--color-info;
    border-color: #ddd;
    color: $--font-color-main;
    &:focus,
    &:hover {
      color: rgba($--font-color-main, 0.7);
    }
    &:active {
      color: rgba($--font-color-main, 0.9);
    }
  }
  &-success {
    background-color: $--color-success;
    border-color: $--color-success;
    &:focus,
    &:hover {
      background-color: rgba($--color-success, 0.7);
    }
    &:active {
      background-color: rgba($--color-success, 0.9);
    }
  }
  &-warning {
    background-color: $--color-warning;
    border-color: $--color-warning;
    &:focus,
    &:hover {
      background-color: rgba($--color-warning, 0.7);
    }
    &:active {
      background-color: rgba($--color-warning, 0.9);
    }
  }
  &-text {
    background-color: $--color-text;
    border-color: $--color-text;
    color: $--color-primary;
    &:focus,
    &:hover {
      color: rgba($--color-primary, 0.7);
    }
    &:active {
      color: rgba($--color-primary, 0.9);
    }
  }
  &-error {
    background-color: $--color-error;
    border-color: $--color-error;
    &:focus,
    &:hover {
      background-color: rgba($--color-error, 0.7);
    }
    &:active {
      background-color: rgba($--color-error, 0.9);
    }
  }

image.png

3. disabled 禁用

该功能实现逻辑比较简单,使用button标签的原生disabled属性即可实现。
主要是写disabled状态对应的样式

<template>
  <button
    class="my-button"
    :disabled="disabled"
    :class="{
      [`my-button-${type}`]: true,
      [`my-button-${type}-disabled`]: disabled,
    }"
    @click="handleClick"
  >
    <slot>按钮</slot>
  </button>
</template>
<script>
// 工具函数,用于判断传入的值是否符合条件
import { oneOf } from '../../utils/assist'

export default {
  name: 'Button',
  data() {
    return {}
  },
  props: {
    type: {
      validator(value) {
        return oneOf(value, [
          'primary',
          'info',
          'success',
          'warning',
          'text',
          'error',
        ])
      },
      type: String,
      default: 'primary',
    },
    disabled: {
      type: Boolean,
      default: false,
    },
  },
  methods: {
    handleClick(event) {
      console.log('按钮被点击', event)
      this.$emit('click', event)
    },
  },
}
</script>
// button.scss 省略部分代码
  &-primary {
    background-color: $--color-primary;
    border-color: $--color-primary;
    // focus,hover的时,背景色变浅
    &:focus,
    &:hover {
      background-color: rgba($--color-primary, 0.7);
      border-color: rgba($--color-primary, 0.7);
    }
    &:active {
      // active时,背景色变浅,且与focus,hover做区分
      background-color: rgba($--color-primary, 0.9);
      border-color: rgba($--color-primary, 0.9);
    }
    // disabled时,变为禁用状态
    &-disabled {
      cursor: not-allowed;
      background-color: rgba($--color-primary, 0.5);
      border-color: rgba($--color-primary, 0.5);
      &:focus,
      &:hover,
      &:active {
        background-color: rgba($--color-primary, 0.5);
        border-color: rgba($--color-primary, 0.5);
      }
    }
  }

根据type设置disabled样式,禁用状态下鼠标样式变为not-allowed,背景色增加50%的不透明度,上面样式代码是primary类型的,其他5个类型写法一样即可。

image.png

4. 尺寸size

设置了四个尺寸: large, medium(默认), small, mini
尺寸的切换同样由绑定class来完成。

// button.vue 省略部分代码
<template>
  <button
    class="my-button"
    :disabled="disabled"
    :class="{
      [`my-button-${type}`]: true,
      [`my-button-${type}-disabled`]: disabled,
      [`my-button-size-${size}`]: true
    }"
    @click="handleClick"
  >
    <slot>按钮</slot>
  </button>
</template>
<script>
export default {
   .......
   props: {
     size: {
      validator(value) {
        return oneOf(value, [
          'large',
          'medium',
          'small',
          'mini'
        ])
      },
      type: String,
      default: 'medium',
    },
   }
}
</script>
// button.scss 省略部分代码
// 以下是size相关样式
  ......
  &-size {
    &-large {
      height: 40px;
      line-height: 40px;
    }
    &-medium {
      height: 36px;
      line-height: 36px;
    }
    &-small {
      height: 32px;
      line-height: 32px;
      font-size: $--font-size-small;
    }
    &-mini {
      height: 28px;
      line-height: 28px;
      font-size: $--font-size-small;
    }
  }

image.png

5. 可设置图标 icon

图标的使用方法参考iconfontFont class引用方式,后续只要维护图标项目库即可做到图标更新。
iconfont上选择图标添加至项目,然后下载到本地。

image.png

下载的文件解压到src/styles/font目录下,然后在styles/index.scss中引用。

// styles/index.scss
@import "./font/iconfont.css";

这样就能在组件上使用了,使用方式是<i class="iconfont icon-xxx" />

// button.vue 省略部分代码
<template>
  <button
    class="my-button"
    :disabled="disabled"
    :class="{
      [`my-button-${type}`]: true,
      [`my-button-${type}-disabled`]: disabled,
      [`my-button-size-${size}`]: true
    }"
    @click="handleClick"
  >
    <i :class="['iconfont', `${icon}`]"></i>
    <slot></slot>
  </button>
</template>
<script>
export default {
  ......
  // 图标
  icon: {
    type: String
  }
  ......
}
</script>

image.png

6. 圆角按钮

功能比较简单,设置border-radius即可。

// button.vue 省略部分代码
<template>
  <button
    class="my-button"
    :disabled="disabled"
    :class="{
      [`my-button-${type}`]: true,
      [`my-button-${type}-disabled`]: disabled,
      [`my-button-size-${size}`]: true,
      [`my-button-size-${size}-round`]: round
    }"
    @click="handleClick"
  >
    <i :class="['iconfont', `${icon}`]"></i>
    <slot></slot>
  </button>
</template>
<script>
export default {
  ......
  // 图标
  round: {
    type: Boolean,
    default: false
  }
  ......
}
</script>
// button.scss 省略部分代码
&-size {
    &-large {
      height: 40px;
      line-height: 40px;
      &-round {
        border-radius: 20px;
      }
    }
    &-medium {
      height: 36px;
      line-height: 36px;
      &-round {
        border-radius: 18px;
      }
    }
    &-small {
      height: 32px;
      line-height: 32px;
      font-size: $--font-size-small;
      &-round {
        border-radius: 16px;
      }
    }
    &-mini {
      height: 28px;
      line-height: 28px;
      font-size: $--font-size-small;
      &-round {
        border-radius: 14px;
      }
    }
  }

image.png

7. 圆形按钮

实现思路与圆角按钮一样,设置border-radius为50%即可。
这里需调整min-widthpadding,使其能达到width=height的条件。

// button.vue 省略部分代码
<template>
  <button
    class="my-button"
    :disabled="disabled"
    :class="{
      [`my-button-${type}`]: true,
      [`my-button-${type}-disabled`]: disabled,
      [`my-button-size-${size}`]: true,
      [`my-button-size-${size}-round`]: round,
      [`my-button-size-${size}-circle`]: circle
    }"
    @click="handleClick"
  >
    <i :class="['iconfont', `${icon}`]"></i>
    <slot></slot>
  </button>
</template>
<script>
export default {
  ......
  // 图标
  circle: {
    type: Boolean,
    default: false
  }
  ......
}
</script>
// button.scss 省略部分代码
......
&-size {
    &-large {
      height: 40px;
      line-height: 40px;
      &-round {
        border-radius: 20px;
      }
      &-circle {
        min-width: 40px;
        border-radius: 50%;
      }
    }
    &-medium {
      height: 36px;
      line-height: 36px;
      &-round {
        border-radius: 18px;
      }
      &-circle {
        min-width: 36px;
        border-radius: 50%;
      }
    }
    &-small {
      height: 32px;
      line-height: 32px;
      font-size: $--font-size-small;
      &-round {
        border-radius: 16px;
      }
      &-circle {
        padding: 0 5px;
        min-width: 32px;
        border-radius: 50%;
      }
    }
    &-mini {
      height: 28px;
      line-height: 28px;
      font-size: $--font-size-small;
      &-round {
        border-radius: 14px;
      }
      &-circle {
        padding: 0 5px;
        min-width: 28px;
        border-radius: 50%;
      }
    }
  }

image.png

8. 加载中 loading

  1. button内部新增一个loading图标,由用户传入的loading参数控制显示隐藏。注意与提供给用户的icon做下切换,使loading图标和icon参数的图标互斥。
  2. loading状态下,按钮被禁用,因此disabled属性由loadingdisabled共同决定。
  3. 用伪元素构造一个layer覆盖在button上面,同时buttonpointer-events设为none,这样能实现按钮无法点击的效果。
// button.vue
<template>
  <button
    class="my-button"
    :disabled="disabled || loading"
    :class="{
      [`my-button-${type}`]: true,
      [`my-button-${type}-disabled`]: disabled,
      [`my-button-size-${size}`]: true,
      [`my-button-size-${size}-round`]: round,
      [`my-button-size-${size}-circle`]: circle,
      [`my-button-loading-layer`]: loading
    }"
    @click="handleClick"
  >
    <i
      class="iconfont icon-loading"
      :class="{ [`my-button-loading`]: true }"
      v-if="loading"
    />
    <i :class="['iconfont', `${icon}`]" v-if="!loading"></i>
    <slot></slot>
  </button>
</template>

<script>
// 工具函数,用于判断传入的值是否符合条件
import { oneOf } from '../../utils/assist'

export default {
  name: 'Button',
  data() {
    return {}
  },
  props: {
    type: {
      validator(value) {
        return oneOf(value, [
          'primary',
          'info',
          'success',
          'warning',
          'text',
          'error',
        ])
      },
      type: String,
      default: 'primary',
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    size: {
      validator(value) {
        return oneOf(value, [
          'large',
          'medium',
          'small',
          'mini'
        ])
      },
      type: String,
      default: 'medium',
    },
    // 图标
    icon: {
      type: String
    },
    // 圆角按钮
    round: {
      type: Boolean,
      default: false
    },
    // 圆形按钮
    circle: {
      type: Boolean,
      default: false
    },
    loading: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    handleClick(event) {
      console.log('按钮被点击', event)
      this.$emit('click', event)
    },
  },
}
</script>
// button.scss 省略部分代码
&-loading-layer {
  pointer-events: none;
  &::before {
    pointer-events: none;
    content: '';
    position: absolute;
    left: -1px;
    top: -1px;
    right: -1px;
    bottom: -1px;
    border-radius: inherit;
    background-color: rgba(255, 255, 255, 0.4);
  }
}

结语

以上是vue知识复习之UI组件的第一篇,给大家介绍了最常见的组件Button的实现过程,运用到的Vue相关知识有父子组件通信,classstyle绑定,slot插槽等。都是基础知识,难度不大,更多的是对css的处理,比如覆盖样式时的css权重设计,loading功能中利用伪元素覆盖在上面使其无法点击等。

脚手架请参考第一篇: juejin.cn/post/701798…
组件示例地址: oversnail.github.io/over-snail-…
git地址: github.com/overSnail/o…

涉及知识点及参考文章

1. (vue):class与style绑定
2. (vue):prop父子组件传参
3. (vue):slot插槽
4. (css):你对CSS权重真的足够了解吗
5. (css):巧用伪元素before和after制作绚丽效果