第四集: 从零开始实现(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 效果
方案一:
- hover 的时候出现灰色蒙层效果
- 被点下时, 按钮缩小动画
&: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)
}
}
效果图
方案二:
- hover 出现金属光泽, bulingbuling 的, 金属光泽.
- 被点下时, 按钮缩小动画
综上分析, 金属光泽流过可能成为一个公用属性, 那么我直接写一个公共的样式吧 “cc-bling”
我的思路:
- 尝试使用渐变的背景颜色, 然后用 ackground-position-x 控制背景的右移
此方案实践起来不舒服, 而且并没有做到 ’ 解耦合 ’ 的设计原则, 所以不用了 - 伪元素, 用一个伪元素加上背景色, 然后把这个元素从左至右划过, 同时用动画把它做得倾斜 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: 防抖与节流
介绍: 这种节流与防抖都是用户自己做的, 至少按钮这种东西本套组件库就是要组件来做.
使用场景:
- 有一次, 我写注册登录页面, 如果用户在注册的时候, 快速的点击了两下, 虽然跳到登录成功页面, 但是会弹出弹框, “ 该手机已被注册 ”, 原来是由于第一个请求把手机注册了, 所以接下来的点击事件的请求后台当然返回的是已注册, 所以这里就需要这样的处理, 每次点击有效之后的点击 n 秒内无效, 防止连点, (防止变速齿轮, 想起了流星蝴蝶剑);
- 我们公司 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
具体的使用
// 后续涉及到防抖与节流的事件也都是走的这套程序;
下一篇
- toast 的封装, 通过封装 toast 让我巩固了好多 ’ 芝士 ’
- 组件的另一种写法
end
这段时间作者辞职, 专心学源码, 练习算法, 重学 js, 重学 node, 重做自己的打包工具, 反正挺忙的, 接下来的时间与精力主要放到这套组件上 同时也会出一些算法啊, 心得之类的文章, 欢迎同学们一起交流, 一起变得更优秀.
github: 链接描述
个人网站: 链接描述