第四集: 从零开始实现一套pc端vue的ui组件库(button组件其二)

 阅读约 13 分钟

第四集: 从零开始实现(button组件2)

本集定位:
之前一直在忙别的事情, 现在终于闲下来, 好好把这个库的文章写一下
本篇目的是承接上文, 把button组件的功能全部实现

1: 为button添加icon
按钮的icon很重要, 现在一般的按钮都带个图案,因为这样符合人脑的快捷思维, 方便理解与记忆.

 <button class="cc-button"
          @touchstart='touchstart($event)'
          :class="[ 
          sizeType,
          type ? 'cc-button--' + type : '',
    {
      'is-left':left,
      'is-right':right,
      'is-centre':centre,
      'is-disabled':disabled,
    }]"
          :type="nativeType"
          @click="click">
    <!-- 图标按钮 -->
    <ccIcon v-if="icon"  // 有没有
            :name='icon' // 有什么样的
            :color="realyIconColor"  // 什么颜色的
            style="margin-right:2px" />  // 与文字有点距离
    <slot />
  </button>

计算realyIconColor
icon有默认的颜色, 但是如果按钮式禁用状态, 那么icon也要相应的置灰

computed: {
    realyIconColor() {
      if (this.disabled) return "#bbbbbb";
      else return this.iconColor;
    }
  }

图片描述

2: icon的位置
很少遇到需要上下左右布局的icon, 如果需要的话.
方案一: 移动<slot>标签, 或是具名slot标签
方案二: 多写几个icon组件, 通过判断决定显示谁

3: hover效果
方案一:

  1. hover的时候出现灰色蒙层效果
  2. 被点下时, 按钮缩小动画
 &:not(.is-disabled) {
        &:active {
            box-shadow: none;
            opacity: 0.7;
            transform: translateX(2px) translateY(2px) scale(0.9);
        }
         &:hover {
            background-color:rgba(0,0,0,0.1)
         }
    }

效果图
图片描述
方案二:

  1. hover出现金属光泽, bulingbuling的,金属光泽.
  2. 被点下时, 按钮缩小动画

综上分析, 金属光泽流过可能成为一个公用属性, 那么我直接写一个公共的样式吧 "cc-bling"
我的思路:

  1. 尝试使用渐变的背景颜色, 然后用 ackground-position-x 控制背景的右移
    此方案实践起来不舒服, 而且并没有做到'解耦合'的设计原则, 所以不用了
  2. 伪元素, 用一个伪元素加上背景色, 然后把这个元素从左至右划过, 同时用动画把它做得倾斜30度
    这样虽然多了个元素, 但是跟父级解耦了, 值得说的一点是, 倾斜之后高度不够盛满了, 简单粗暴的
    方式就是把高度定位多一些的, 这样旋转也不会导致高度不够的状态了.

let'go

<button class="cc-button"
  :class="[
    {
      'is-bling':bling, // 加了一个接受 是否金属光泽的属性
      'is-left':left,
      'is-right':right,
      'is-centre':centre,
      'is-disabled':disabled,
    }]"
  </button>
 bling:Boolean, // 条纹

在button.scss;里面添加

@at-root {
        @include commonType(cc-button--);
        .is-bling {
        //此属性名在hover的时候才进行bling操作
            &:hover {
                @extend .cc-bling;
            }
        }
    };

animation.scss
定义一个从左至右的动画

  @keyframes bling{
    0% {
      left: 0;
    }
    100% {
      left: 300%;
    }
  }

extend.scss
定义具体的样式把

.cc-bling {
    &:after {
        content: '';
        position: absolute;
        background-image: linear-gradient(to right, rgb(232, 229, 229), white);
        left: 0;
        top: -20px; // 避免倾斜的时候头部漏出尖角
        width: 15px;
        height: calc(100% + 30px); // 避免旋转时候出现高度不够的情况
        transform: rotate(-30deg);
        animation-name: bling;
        animation-duration: 1s; // 总用时
        animation-iteration-count: infinite; // 无限循环
        animation-timing-function: linear; // 匀速
    }
}

效果图, 是动态的, 从左至右划过.
图片描述
4: 防抖与节流
介绍: 这种节流与防抖都是用户自己做的, 至少按钮这种东西本套组件库就是要组件来做.
使用场景:

  1. 有一次,我写注册登录页面, 如果用户在注册的时候, 快速的点击了两下, 虽然跳到登录成功页面, 但是会弹出弹框, "该手机已被注册", 原来是由于第一个请求把手机注册了, 所以接下来的点击事件的请求后台当然返回的是已注册, 所以这里就需要这样的处理, 每次点击有效之后的点击n秒内无效, 防止连点, (防止变速齿轮, 想起了流星蝴蝶剑);
  2. 我们公司19年搞了个抢购活动, 可能发生这样的场景, 用户守着抢购按钮, 不断地点击, 我们web端需要在每次用户点击的时候询问后台'活动开始了么?', 没开始就给用户弹tosat(下一章就做这个组件了),开始了才进入活动也或是订单页, 但是, 用户如果是个金手指, 疯狂把玩抢购键, 那就会发出大量的请求了,

我来说一下:
第一: 为什么不使用后台返回的活动开始时间与本地的事件进行比对??

   原因是用户的本地时间并不是一个值得信赖的量, 平时可以作为一个参考, 但是像抢购这种分秒必争的事情, 就要让用户与服务器时间同步起来了.

第二: 为什么不在第一次请求之后把时间戳记录下来, 本地用计时器模拟计时??

   原因是某些浏览器环境下, 用户切出程序之类的一些操作, 他会杀掉计时器, 导致计时不准, 而这种问题暂时无法解决, 也监控不到, 所以为了分秒必争保险起见.

最后只能是每次都请求一下, 那就需要, 比如说 每600毫秒内, 只让用户的点击有效一次.

我们就不能单纯的写click事件了,要抽离出来进行蹂躏☺️.
dom

 <button class="cc-button"
     @click="click">
  </button>

接值

props: {
    ...,
    shake: Number, // 防抖的秒数
    throttle: Number, // 节流, 请输入秒数
    clickId: [String, Number], // 相同id的组件走一套计时.
}

事件

 click() {
    // 根据用户的输入, 来决定怎么计时.
    // 值得一提的是, clickId 相同的话我们是统一计时的, 
    // 比如说: 三个按钮, 点了其中一个, 其他的几个在规定时间内,都会不可点击
      let clickType, num;
      if (this.throttle) {
        clickType = 1;
        num = this.throttle;
      } else if (this.shake && this.shake > 0) {
        clickType = 2;
        num = this.shake;
      } else if (this.shake && this.shake < 0) {
        clickType = 3;
        num = this.shake * -1;
      }
      prevent(
        this.clickId,
        () => {
          this.$emit("click");
        },
        num,
        clickType
      );
    },

在之前的工作中自己写过一个防抖与节流的函数, 这次就直接拿来用了

let preventList = {}
const prevent = function(id, obj, time, model = 1) {
  switch (model) {
    case 1:
      model1(id, obj, time)
      break;
    case 2:
      model2(id, obj, time)
      break;
    case 3:
      model3(id, obj, time)
      break;
    default:
  }
}

// 模式1 不管点多少下每隔time秒,触发一次
function model1(id, obj, time) {
  if (preventList['can' + id]) return
  obj()
  preventList['can' + id] = true
  preventList['time' + id] = setTimeout(() => {
    preventList['can' + id] = false
  }, time)
}

// 模式2 每次动作都有time的延时再执行,也就是所有点击完事的时候执行一个
function model2(id, obj, time) {
  clearTimeout(preventList['time' + id])
  preventList['time' + id] = setTimeout(() => {
    obj()
  }, time)
}

// 默认的模式, 模式3, 第一下点击触发, 之后时间内不触发
function model3(id, obj, time) {
  if (preventList['can' + id]) {
    clearTimeout(preventList['time' + id])
  } else {
    obj()
    preventList['can' + id] = true
  }
  preventList['time' + id] = setTimeout(() => {
    preventList['can' + id] = false
  }, time)

}

export default prevent

具体的使用

图片描述

//后续涉及到防抖与节流的事件也都是走的这套程序;

下一篇

  1. toast的封装, 通过封装toast让我巩固了好多'芝士'
  2. 组件的另一种写法

end
这段时间作者辞职, 专心学源码, 练习算法, 重学js, 重学node,重做自己的打包工具, 反正挺忙的,接下来的时间与精力主要放到这套组件上 同时也会出一些算法啊, 心得之类的文章, 欢迎同学们一起交流, 一起变得更优秀.

github:链接描述
个人网站: 链接描述

阅读 720更新于 7月21日
推荐阅读
目录