小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
TIP 👉 红雨随心翻作浪,青山着意化为桥。《七律·送瘟神》
前言
在我们日常项目开发中,我们经常会写一些按钮,所以封装了这款按钮组件。按钮组件
属性
1. text 按钮文字
可以通过btnText设置按钮文字,也可以像button标签一样通过标签内容设置按钮文字
2. type 按钮类型
控制按钮样式
- primary 蓝色按钮
- disabled 灰色按钮
- success 绿色按钮
- danger 红色按钮
3. size 按钮大小
- xs 特小
- s 小
- m 中
- l 大
- xl 特大
- full 100%宽(仅移动端支持,且为移动端默认大小)
3. icon 按钮图标
svg图标库的图标名称,即assets/icon目录下svg图片的名称(不包含后缀)
4. iconPosition 图标的位置
- start 图标在左侧,图标右边有空白
- end 图标在右侧,图标左边有空白
5. charLength 汉字个数
- 值为2到10的数字,宽度等同于有指定个数汉字的按钮宽度
6. throttle 是否启用函数节流(默认为true,启用)
值为布尔类型,如果启动函数节流在指定的时间内只触发一次点击事件
7. throttleTime 函数节流等待时间
值为数字类型,单位:毫秒,默认值为:1000
8. disabled 按钮是否可用
- true 按钮可用,触发click事件
- false 按钮不可用,不触发click事件
示例
<template>
<div class="button-demo">
<div class="button-demo">
<BaseButton @click="clickHandle">默认按钮</BaseButton><br/>
<BaseButton type="primary" @click="clickHandle">主题色</BaseButton><br/>
<BaseButton :disabled="true" @click="clickHandle">不可用</BaseButton><br/>
<BaseButton size="xl" @click="clickHandle">超大号</BaseButton><br/>
<BaseButton size="l" @click="clickHandle">大号</BaseButton><br/>
<BaseButton size="m" @click="clickHandle">中号</BaseButton><br/>
<BaseButton size="s" @click="clickHandle">小号</BaseButton><br/>
<BaseButton size="xs" @click="clickHandle">超小号</BaseButton><br/>
<BaseButton size="m" @click="clickHandle">三个字</BaseButton><br/>
<BaseButton :charLength="3" @click="clickHandle">确 定</BaseButton><br/>
<BaseButton :charLength="3" @click="clickHandle">O K</BaseButton><br/>
<BaseButton @click="clickHandle" class="btn-out">自定义样式</BaseButton><br/>
<BaseButton type="primary" @click="clickHandle" icon="home"></BaseButton><br/>
<BaseButton type="primary" @click="clickHandle" icon="search" iconPosition="end">
<span>查询</span>
</BaseButton><br/>
<BaseButton @click="clickHandle" icon="home" iconPosition="start">
<span>返回首页</span>
</BaseButton>
<BaseButton @click="clickHandle" :throttle="true">函数节流3秒</BaseButton>
<BaseButton @click="clickHandle" :throttleTime="1000">函数节流1秒</BaseButton>
<BaseButton @click="clickHandle" :throttle="false">禁用函数节流</BaseButton>
</div>
</div>
</template>
<script>
import BaseButton from '@/components/base/button/index.vue'
export default {
name: 'ButtonDemo',
components: {
BaseButton
},
data () {
return {}
},
methods: {
clickHandle (event) {
let text = event.currentTarget.innerText
this.$toast(`点击了“${text}”按钮`)
}
}
}
</script>
<style lang="scss" scoped>
.button-demo{
text-align: center;
line-height: 120px;
}
button.btn-out{
width: 360px;
background-color: #333333;
border-radius: 20px;
color: #fff;
}
</style>
实现Button.vue
<template>
<button class="btn"
:class="[computedType, computedSize, computedCharLength]"
:disabled="disabled" @click="doClick">
<canvas class="zk-ripple" @click="ripple"></canvas>
<Icon v-if="icon && iconPosition==='start'" :name="icon" class="start-icon"></Icon>
<slot>{{ text }}</slot>
<Icon v-if="icon && iconPosition==='end'" :name="icon" class="end-icon"></Icon>
<Icon v-if="icon && iconPosition!=='start' && iconPosition!=='end'" :name="icon"></Icon>
</button>
</template>
<script>
import { throttle } from '@/assets/js/utils'
import { getStyle, getStyleNumber } from './utils'
export default {
name: 'Button',
props: {
// 按钮文字
text: {
type: String,
default: ''
},
// 按钮类型:primary、disabled、success、danger
type: {
type: String,
default: ''
},
// 按钮大小:xs、s、m、l、xl、full
size: {
type: String,
default: 'full'
},
// 按钮图标
icon: {
type: String
},
// 图标位置:start、end
iconPosition: {
type: String
},
// 宽度等同于有指定个数汉字的按钮宽度(支持2到10个汉字数)
charLength: {
type: Number
},
// 是否启用函数节流
throttle: {
type: Boolean,
default: true
},
// 函数节流等待时间,单位:毫秒
throttleTime: {
type: Number,
default: 1000 // 1秒
},
// 按钮是否可用, 如果为true不触发点击事件
disabled: {
type: Boolean,
default: false
},
// 动画速度
speed: {
type: Number,
default: 20
},
// 动画透明度
opacity: {
type: Number,
default: 0.4
}
},
data () {
return {
color: '',
radius: 0,
oCanvas: null,
context: null,
initialized: false,
speedOpacity: 0,
timer: null,
origin: {},
// 函数节流方式触发点击事件
throttleEmitClick: throttle(function (vm, event) {
vm.$emit('click', event)
}, this.throttleTime, { trailing: false })
}
},
computed: {
// 控制按钮的类型
computedType () {
if (this.disabled) {
return 'btn-disabled'
}
return this.type ? `btn-${this.type}` : ''
},
// 控制按钮大小尺寸
computedSize () {
return this.size ? `btn-${this.size}` : ''
},
// 控制按钮宽度等同于有指定个数汉字的按钮
computedCharLength () {
return this.charLength && this.charLength >= 2 && this.charLength <= 10 ? `btn-char-length${this.charLength}` : ''
}
},
methods: {
init (el) {
const oBtn = el.parentElement
this.color = getStyle(el.parentElement, 'color')
const w = getStyleNumber(oBtn, 'width')
// 透明度的速度
this.speedOpacity = (this.speed / w) * this.opacity
// canvas 宽和高
el.width = w
el.height = getStyleNumber(oBtn, 'height')
this.context = el.getContext('2d')
},
ripple (event) {
// 清除上次没有执行的动画
if (this.timer) {
window.cancelAnimationFrame(this.timer)
}
this.el = event.target
// 不可用或者按钮宽度小于180时不显示动画
if (this.disabled || this.el.width < 180) return
// 执行初始化
if (!this.initialized) {
this.initialized = true
this.init(this.el)
}
this.radius = 0
// 点击坐标原点
this.origin.x = event.offsetX
this.origin.y = event.offsetY
this.context.clearRect(0, 0, this.el.width, this.el.height)
this.el.style.opacity = this.opacity
this.draw()
},
draw () {
this.context.beginPath()
// 绘制波纹
this.context.arc(this.origin.x, this.origin.y, this.radius, 0, 2 * Math.PI, false)
this.context.fillStyle = this.color
this.context.fill()
// 定义下次的绘制半径和透明度
this.radius += this.speed
this.el.style.opacity -= this.speedOpacity
// 通过判断半径小于元素宽度或者还有透明度,不断绘制圆形
if (this.radius < this.el.width || this.el.style.opacity > 0) {
this.timer = window.requestAnimationFrame(this.draw)
} else {
// 清除画布
this.context.clearRect(0, 0, this.el.width, this.el.height)
this.el.style.opacity = 0
}
},
doClick (event) {
if (!this.disabled) {
if (this.throttle) {
this.throttleEmitClick(this, event)
} else {
this.$emit('click', event)
}
}
}
},
destroyed () {
// 清除上次没有执行的动画
if (this.timer) {
window.cancelAnimationFrame(this.timer)
this.timer = null
}
}
}
</script>
<style lang="scss" scoped px2rem="false">
$btn-font-size: 12px;
$btn-padding: 15px;
.btn {
position: relative;
padding: 0 $btn-padding;
height: 30px;
box-sizing: content-box;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
background-color: #FFF;
border: 1px solid $border-color-base;
border-radius: 5px;
font-size: $btn-font-size;
color: #666;
transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
overflow: hidden;
outline: none;
&+.btn {
margin-left: 10px;
}
&>span, &>.fa-icon {
vertical-align: middle;
}
& .start-icon {
margin-right: 3px;
}
& .end-icon {
margin-left: 3px;
}
}
.btn:active {
box-shadow: 0 1px 3px -1px rgba(0, 0, 0, .5), 0 0 5px 0 rgba(0, 0, 0, 0.12);
}
.zk-ripple {
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.btn-primary {
color: #fff;
@include primary-background-color();
@include primary-border-color();
}
.btn-primary:hover {
color: #fff;
@include primary-background-color();
@include primary-border-color();
}
.btn-primary.disabled, .btn-primary:disabled {
@include primary-background-color();
@include primary-border-color();
}
.btn-success {
color: #fff;
background-color: $color-success;
border-color: $color-success;
}
.btn-success:hover {
color: #fff;
background-color: $color-success;
border-color: $color-success;
}
.btn-success.disabled, .btn-success:disabled {
background-color: $color-success;
border-color: $color-success;
}
.btn-danger {
color: #fff;
background-color: $color-danger;
border-color: $color-danger;
}
.btn-danger :hover {
color: #fff;
background-color: $color-danger;
border-color: $color-danger;
}
.btn-danger .disabled, .btn-danger:disabled {
background-color: $color-danger;
border-color: $color-danger;
}
.btn-xs{
padding: 0 ($btn-padding - 6px);
height: 22px;
font-size: 12px;
}
.btn-s{
padding: 0 ($btn-padding - 4px);
height: 26px;
font-size: 13px;
}
.btn-m{
}
.btn-l{
padding: 0 ($btn-padding + 4px);
height: 32px;
font-size: 15px;
}
.btn-xl{
padding: 0 ($btn-padding + 6px);
height: 34px;
font-size: 16px;
}
button.btn-char-length2{
width: 2em;
}
button.btn-char-length3{
width: 3em;
}
button.btn-char-length4{
width: 4em;
}
button.btn-char-length5{
width: 5em;
}
button.btn-char-length6{
width: 6em;
}
button.btn-char-length7{
width: 7em;
}
button.btn-char-length8{
width: 8em;
}
button.btn-char-length9{
width: 9em;
}
button.btn-char-length10{
width: 10em;
}
button.btn-disabled {
color: #fff;
background-color: #ccc;
border-color: #ccc;
}
</style>
// 下面注释勿删,用于根据配置文件 isMobile 配置项判断是否支持移动端,如果不支持,注释掉该样式
/* [auto html command START {isMobile=false}] */
<style lang="scss" scoped>
$btn-font-size: 32px;
$btn-padding: 20px;
@media (max-width: $max-mobile-width) {
.btn {
padding: 0 $btn-padding;
height: 84px;
box-sizing: border-box;
font-size: $btn-font-size;/*yes*/
border: none;
background-color: #f3f3f3;
}
.btn-full{
width: 100%;
}
.btn-xl{
width: 600px;
}
.btn-l{
width: 420px;
}
.btn-m{
height: 80px;
}
.btn-s{
height: 72px;
font-size: 28px;/*yes*/
}
.btn-xs{
height: 64px;
font-size: 24px;/*yes*/
}
button.btn-char-length2{
width: $btn-font-size * 2 + $btn-padding * 2;
}
button.btn-char-length3{
width: $btn-font-size * 3 + $btn-padding * 2;
}
button.btn-char-length4{
width: $btn-font-size * 4 + $btn-padding * 2;
}
button.btn-char-length5{
width: $btn-font-size * 5 + $btn-padding * 2;
}
button.btn-char-length6{
width: $btn-font-size * 6 + $btn-padding * 2;
}
button.btn-char-length7{
width: $btn-font-size * 7 + $btn-padding * 2;
}
button.btn-char-length8{
width: $btn-font-size * 8 + $btn-padding * 2;
}
button.btn-char-length9{
width: $btn-font-size * 9 + $btn-padding * 2;
}
button.btn-char-length10{
width: $btn-font-size * 10 + $btn-padding * 2;
}
}
</style>
/* [auto html command END {isMobile=false}] */
utils.js
/**
* 根据属性,获取元素的样式值
* @param el 元素
* @param attr 属性
* @param pseudoClass 元素伪类
* @returns {*}
*/
export function getStyle (el, attr, pseudoClass = null) {
return window.getComputedStyle(el, pseudoClass)[attr]
}
/**
* 获取属性,并且属性值是数字,而不是字符串
* @param el 元素
* @param attr 属性
* @param pseudoClass 元素伪类
* @returns {Number}
*/
export function getStyleNumber (el, attr, pseudoClass = null) {
try {
const val = getStyle(el, attr, pseudoClass)
return parseFloat(val)
} catch (e) {
console.error(e)
}
}
节流js
/**
* 函数节流
* @param func
* @param wait
* @param options
* @returns {throttled}
* 参考:http://underscorejs.org/#throttle
*/
export function throttle (func, wait, options) {
let timeout, context, args, result
let previous = 0
if (!options) options = {}
let later = function () {
previous = options.leading === false ? 0 : nowTime()
timeout = null
result = func.apply(context, args)
if (!timeout) context = args = null
}
let throttled = function () {
let now = nowTime()
if (!previous && options.leading === false) previous = now
let remaining = wait - (now - previous)
context = this
args = arguments
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
previous = now
result = func.apply(context, args)
if (!timeout) context = args = null
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining)
}
return result
}
throttled.cancel = function () {
clearTimeout(timeout)
previous = 0
timeout = context = args = null
}
return throttled
}
感谢评论区大佬的点拨。