524 lines
14 KiB
Vue
524 lines
14 KiB
Vue
|
<template>
|
||
|
<view class="tn-fab-class tn-fab" @touchmove.stop.prevent>
|
||
|
<view
|
||
|
class="tn-fab__box"
|
||
|
:class="{'tn-fab--right': left === 'auto'}"
|
||
|
:style="{
|
||
|
left: $tn.string.getLengthUnitValue(fabLeft || left),
|
||
|
right: $tn.string.getLengthUnitValue(fabRight || right),
|
||
|
bottom: $tn.string.getLengthUnitValue(fabBottom || bottom),
|
||
|
zIndex: elZIndex
|
||
|
}"
|
||
|
>
|
||
|
<view
|
||
|
v-if="visibleSync"
|
||
|
class="tn-fab__btns"
|
||
|
:class="[`tn-fab__btns__animation--${animationType}`,
|
||
|
showFab ? `tn-fab__btns--visible--${animationType}` : ''
|
||
|
]"
|
||
|
>
|
||
|
<view
|
||
|
v-for="(item, index) in btnList"
|
||
|
:key="index"
|
||
|
class="tn-fab__item"
|
||
|
:class="[
|
||
|
`tn-fab__item__animation--${animationType}`,
|
||
|
{'tn-fab__item--left': right === 'auto'}
|
||
|
]"
|
||
|
:style="[fabItemStyle(index)]"
|
||
|
@tap.stop="handleClick(index)"
|
||
|
>
|
||
|
<!-- 带图标或者图片时显示的文字信息 -->
|
||
|
<view
|
||
|
v-if="animationType !== 'around' && (item.imgUrl || item.icon)"
|
||
|
:class="[left === 'auto' ? 'tn-fab__item__text--right' : 'tn-fab__item__text--left']"
|
||
|
:style="{
|
||
|
color: item.textColor || '#FFF',
|
||
|
fontSize: $tn.string.getLengthUnitValue(item.textSize || 28)
|
||
|
}"
|
||
|
>{{ item.text || '' }}</view>
|
||
|
|
||
|
<!-- 带图片或者图标时的图片、图标信息 -->
|
||
|
<view
|
||
|
class="tn-fab__item__btn"
|
||
|
:class="[!item.bgColor ? backgroundColorClass : '']"
|
||
|
:style="{
|
||
|
width: $tn.string.getLengthUnitValue(width),
|
||
|
height: $tn.string.getLengthUnitValue(height),
|
||
|
lineHeight: $tn.string.getLengthUnitValue(height),
|
||
|
backgroundColor: item.bgColor || backgroundColorStyle || '#01BEFF',
|
||
|
borderRadius: $tn.string.getLengthUnitValue(radius)
|
||
|
}"
|
||
|
>
|
||
|
<!-- 无图片和无图标时只显示文字 -->
|
||
|
<view
|
||
|
v-if="!item.imgUrl && !item.icon"
|
||
|
class="tn-fab__item__btn__title"
|
||
|
:style="{
|
||
|
color: item.textColor || '#fff',
|
||
|
fontSize: $tn.string.getLengthUnitValue(item.textSize || 28)
|
||
|
}"
|
||
|
>{{ item.text || '' }}</view>
|
||
|
<!-- 图标 -->
|
||
|
<view
|
||
|
v-if="item.icon"
|
||
|
class="tn-fab__item__btn__icon"
|
||
|
:class="[`tn-icon-${item.icon}`]"
|
||
|
:style="{
|
||
|
color: item.iconColor || '#fff',
|
||
|
fontSize: $tn.string.getLengthUnitValue(item.iconSize || iconSize || 64)
|
||
|
}"
|
||
|
></view>
|
||
|
<!-- 图片 -->
|
||
|
<image
|
||
|
v-else-if="!item.icon && item.imgUrl"
|
||
|
class="tn-fab__item__btn__image"
|
||
|
:style="{
|
||
|
width: $tn.string.getLengthUnitValue(item.imgWidth || 64),
|
||
|
height: $tn.string.getLengthUnitValue(item.imgHeight || 64),
|
||
|
}"
|
||
|
:src="item.imgUrl"
|
||
|
></image>
|
||
|
</view>
|
||
|
</view>
|
||
|
</view>
|
||
|
|
||
|
<view
|
||
|
class="tn-fab__item__btn tn-fab__item__btn--fab"
|
||
|
:class="[backgroundColorClass, fontColorClass, {'tn-fab__item__btn--active': showFab}]"
|
||
|
:style="{
|
||
|
width: $tn.string.getLengthUnitValue(width),
|
||
|
height: $tn.string.getLengthUnitValue(height),
|
||
|
backgroundColor: backgroundColorStyle || !backgroundColorClass ? '#01BEFF' : '',
|
||
|
color: fontColorStyle || '#fff',
|
||
|
borderRadius: $tn.string.getLengthUnitValue(radius),
|
||
|
zIndex: elZIndex - 1
|
||
|
}"
|
||
|
@tap.stop="fabClick"
|
||
|
>
|
||
|
<slot>
|
||
|
<view class="tn-fab__item__btn__icon" :class="[`tn-icon-${icon}`]" :style="{fontSize: $tn.string.getLengthUnitValue(iconSize || 64)}"></view>
|
||
|
</slot>
|
||
|
</view>
|
||
|
</view>
|
||
|
<view v-if="visibleSync && showMask" class="tn-fab__mask" :class="{'tn-fab__mask--visible': showFab}" :style="{zIndex: elZIndex - 3}" @tap="clickMask()"></view>
|
||
|
</view>
|
||
|
</template>
|
||
|
|
||
|
<script>
|
||
|
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||
|
export default {
|
||
|
name: 'tn-fab',
|
||
|
mixins: [componentsColorMixin],
|
||
|
props: {
|
||
|
// 按钮列表
|
||
|
// {
|
||
|
// // 背景颜色
|
||
|
// bgColor: '#fff',
|
||
|
// // 图片地址
|
||
|
// imgUrl: '',
|
||
|
// // 图片宽度
|
||
|
// imgWidth: 60,
|
||
|
// // 图片高度
|
||
|
// imgHeight: 60,
|
||
|
// // 图标名称
|
||
|
// icon: '',
|
||
|
// // 图标尺寸
|
||
|
// iconSize: 60,
|
||
|
// // 图标颜色
|
||
|
// iconColor: '#fff',
|
||
|
// // 提示文字
|
||
|
// text: '',
|
||
|
// // 文字大小
|
||
|
// textSize: 30,
|
||
|
// // 字体颜色
|
||
|
// textColor: '#fff'
|
||
|
// }
|
||
|
btnList: {
|
||
|
type: Array,
|
||
|
default() {
|
||
|
return []
|
||
|
}
|
||
|
},
|
||
|
// 自定义悬浮按钮内容
|
||
|
customBtn: {
|
||
|
type: Boolean,
|
||
|
default: false
|
||
|
},
|
||
|
// 悬浮按钮的宽度
|
||
|
width: {
|
||
|
type: [String, Number],
|
||
|
default: 88
|
||
|
},
|
||
|
// 悬浮按钮的高度
|
||
|
height: {
|
||
|
type: [String, Number],
|
||
|
default: 88
|
||
|
},
|
||
|
// 图标大小
|
||
|
iconSize: {
|
||
|
type: [String, Number],
|
||
|
default: 64
|
||
|
},
|
||
|
// 图标名称
|
||
|
icon: {
|
||
|
type: String,
|
||
|
default: 'open'
|
||
|
},
|
||
|
// 按钮圆角
|
||
|
radius: {
|
||
|
type: [String, Number],
|
||
|
default: '50%'
|
||
|
},
|
||
|
// 按钮距离左边的位置
|
||
|
left: {
|
||
|
type: [String, Number],
|
||
|
default: 'auto'
|
||
|
},
|
||
|
// 按钮距离右边的位置
|
||
|
right: {
|
||
|
type: [String, Number],
|
||
|
default: 'auto'
|
||
|
},
|
||
|
// 按钮距离底部的位置
|
||
|
bottom: {
|
||
|
type: [String, Number],
|
||
|
default: 100
|
||
|
},
|
||
|
// 展示动画类型 up 往上展示 around 环绕
|
||
|
animationType: {
|
||
|
type: String,
|
||
|
default: 'up'
|
||
|
},
|
||
|
// 当动画为圆环时,每个弹出按钮之间的距离, 单位px
|
||
|
aroundBtnDistance: {
|
||
|
type: Number,
|
||
|
default: 10
|
||
|
},
|
||
|
zIndex: {
|
||
|
type: Number,
|
||
|
default: 0
|
||
|
},
|
||
|
// 显示遮罩
|
||
|
showMask: {
|
||
|
type: Boolean,
|
||
|
default: true
|
||
|
},
|
||
|
// 点击遮罩是否可以关闭
|
||
|
maskCloseable: {
|
||
|
type: Boolean,
|
||
|
default: true
|
||
|
}
|
||
|
},
|
||
|
data() {
|
||
|
return {
|
||
|
showFab: false,
|
||
|
visibleSync: false,
|
||
|
timer: null,
|
||
|
fabLeft: 0,
|
||
|
fabRight: 0,
|
||
|
fabBottom: 0,
|
||
|
fabBtnInfo: {
|
||
|
width: 0,
|
||
|
height: 0,
|
||
|
left: 0,
|
||
|
right: 0,
|
||
|
bottom: 0
|
||
|
},
|
||
|
systemInfo: {
|
||
|
width: 0,
|
||
|
height: 0
|
||
|
},
|
||
|
updateProps: false
|
||
|
}
|
||
|
},
|
||
|
computed: {
|
||
|
elZIndex() {
|
||
|
return this.zIndex || this.$tn.zIndex.fab
|
||
|
},
|
||
|
propsData() {
|
||
|
return [this.width, this.height, this.left, this.right, this.bottom]
|
||
|
},
|
||
|
fabItemStyle() {
|
||
|
return (index) => {
|
||
|
let style = {
|
||
|
zIndex: this.elZIndex - 2
|
||
|
}
|
||
|
if (this.animationType === 'up' || !this.showFab) {
|
||
|
return style
|
||
|
}
|
||
|
let base = 1
|
||
|
if (this.left === 'auto') {
|
||
|
base = 1
|
||
|
} else if (this.right === 'auto') {
|
||
|
base = -1
|
||
|
}
|
||
|
style.transform = `rotate(${base * index * 60}deg) translateX(${(this.aroundBtnDistance + this.fabBtnInfo.width) * (-(base))}px)`
|
||
|
return style
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
watch: {
|
||
|
propsData() {
|
||
|
// 更新按钮信息
|
||
|
this.updateProps = true
|
||
|
}
|
||
|
},
|
||
|
mounted() {
|
||
|
this.$nextTick(() => {
|
||
|
this.getFabBtnRectInfo()
|
||
|
})
|
||
|
},
|
||
|
beforeDestroy() {
|
||
|
if (this.timer) {
|
||
|
clearTimeout(this.timer)
|
||
|
}
|
||
|
},
|
||
|
methods: {
|
||
|
// 按钮点击事件
|
||
|
handleClick(index) {
|
||
|
this.close()
|
||
|
this.$emit('click', {index: index})
|
||
|
},
|
||
|
// 点击悬浮按钮
|
||
|
fabClick() {
|
||
|
if (this.showFab) {
|
||
|
this.close()
|
||
|
} else {
|
||
|
// console.log(this.visibleSync);
|
||
|
if (this.visibleSync) {
|
||
|
this.visibleSync = false
|
||
|
return
|
||
|
}
|
||
|
this.open()
|
||
|
}
|
||
|
},
|
||
|
// 点击遮罩
|
||
|
clickMask() {
|
||
|
if (!this.showMask || !this.maskCloseable) return
|
||
|
this.close()
|
||
|
},
|
||
|
|
||
|
open() {
|
||
|
this.change('visibleSync', 'showFab', true)
|
||
|
this.translateFabPosition()
|
||
|
},
|
||
|
close() {
|
||
|
this.change('showFab', 'visibleSync', false)
|
||
|
this.fabLeft = 0
|
||
|
this.fabRight = 0
|
||
|
this.fabBottom = 0
|
||
|
},
|
||
|
// 关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
|
||
|
// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
|
||
|
change(param1, param2, status) {
|
||
|
this[param1] = status
|
||
|
if (status) {
|
||
|
// #ifdef H5 || MP
|
||
|
this.timer = setTimeout(() => {
|
||
|
this[param2] = status
|
||
|
this.$emit(status ? 'open' : 'close')
|
||
|
clearTimeout(this.timer)
|
||
|
}, 10)
|
||
|
// #endif
|
||
|
// #ifndef H5 || MP
|
||
|
this.$nextTick(() => {
|
||
|
this[param2] = status
|
||
|
this.$emit(status ? 'open' : 'close')
|
||
|
})
|
||
|
// #endif
|
||
|
} else {
|
||
|
this.timer = setTimeout(() => {
|
||
|
this[param2] = status
|
||
|
this.$emit(status ? 'open' : 'close')
|
||
|
clearTimeout(this.timer)
|
||
|
}, 250)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/******************** 旋转动画相关函数 ********************/
|
||
|
// 获取按钮的信息
|
||
|
async getFabBtnRectInfo() {
|
||
|
const systemInfo = uni.getSystemInfoSync()
|
||
|
const btnRectInfo = await this._tGetRect('.tn-fab__item__btn--fab')
|
||
|
if (!btnRectInfo) {
|
||
|
setTimeout(() => {
|
||
|
this.getFabBtnRectInfo()
|
||
|
}, 10)
|
||
|
return
|
||
|
}
|
||
|
console.log(btnRectInfo);
|
||
|
this.systemInfo = {
|
||
|
width: systemInfo.windowWidth,
|
||
|
height: systemInfo.windowHeight
|
||
|
}
|
||
|
this.fabBtnInfo.width = btnRectInfo.width
|
||
|
this.fabBtnInfo.height = btnRectInfo.height
|
||
|
this.fabBtnInfo.left = btnRectInfo.left
|
||
|
this.fabBtnInfo.right = btnRectInfo.right
|
||
|
this.fabBtnInfo.bottom = btnRectInfo.bottom
|
||
|
},
|
||
|
// 更新悬浮按钮的位置
|
||
|
translateFabPosition() {
|
||
|
if (this.updateProps) {
|
||
|
this.getFabBtnRectInfo()
|
||
|
this.updateProps = false
|
||
|
}
|
||
|
if (this.animationType === 'up') return
|
||
|
// 按钮组的宽度
|
||
|
const btnGroupWidth = this.fabBtnInfo.width + this.aroundBtnDistance + 10
|
||
|
// 判断当前按钮是在左边还是右边
|
||
|
if (this.left === 'auto' && btnGroupWidth > this.systemInfo.width - this.fabBtnInfo.right) {
|
||
|
// 距离不够需要移动
|
||
|
this.fabRight = btnGroupWidth + 'px'
|
||
|
} else if (this.right === 'auto' && btnGroupWidth > this.fabBtnInfo.left) {
|
||
|
this.fabLeft = btnGroupWidth + 'px'
|
||
|
}
|
||
|
|
||
|
if (btnGroupWidth > this.systemInfo.height - this.fabBtnInfo.bottom) {
|
||
|
this.fabBottom = btnGroupWidth + 'px'
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<style lang="scss" scoped>
|
||
|
|
||
|
.tn-fab {
|
||
|
&__box {
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
align-items: flex-start;
|
||
|
flex-direction: column;
|
||
|
position: fixed;
|
||
|
transition: all 0.25s ease-in-out;
|
||
|
}
|
||
|
|
||
|
&--right {
|
||
|
align-items: flex-end;
|
||
|
}
|
||
|
|
||
|
&__btns {
|
||
|
transition: all 0.25s cubic-bezier(0,.13,0,1.43);
|
||
|
transform-origin: 80% bottom;
|
||
|
|
||
|
&__animation--up {
|
||
|
opacity: 0;
|
||
|
transform: translateY(100%);
|
||
|
}
|
||
|
&__animation--around {
|
||
|
position: absolute;
|
||
|
top: 0;
|
||
|
left: 0;
|
||
|
}
|
||
|
|
||
|
&--visible--up {
|
||
|
// visibility: visible;
|
||
|
opacity: 1;
|
||
|
transform: translateY(0);
|
||
|
}
|
||
|
&--visible--around {
|
||
|
// visibility: visible;
|
||
|
// opacity: 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
&__item {
|
||
|
display: flex;
|
||
|
justify-content: flex-end;
|
||
|
align-items: center;
|
||
|
padding-bottom: 20rpx;
|
||
|
|
||
|
&__animation--around {
|
||
|
position: absolute;
|
||
|
top: 0;
|
||
|
left: 0;
|
||
|
transition: transform 0.25s ease-in-out;
|
||
|
transform-origin: 50% 50%;
|
||
|
padding-bottom: 0 !important;
|
||
|
}
|
||
|
|
||
|
&--left {
|
||
|
flex-flow: row-reverse;
|
||
|
}
|
||
|
|
||
|
&__text {
|
||
|
&--left {
|
||
|
padding-left: 14rpx;
|
||
|
}
|
||
|
&--right {
|
||
|
padding-right: 14rpx;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
&__btn {
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
box-shadow: 0 0 5rpx 2rpx rgba(0, 0, 0, 0.07);
|
||
|
transition: all 0.2s linear;
|
||
|
|
||
|
&--active {
|
||
|
animation-name: fab-button-animation;
|
||
|
animation-duration: 0.2s;
|
||
|
animation-timing-function: cubic-bezier(0,.13,0,1.43);
|
||
|
}
|
||
|
|
||
|
&__title {
|
||
|
width: 90%;
|
||
|
text-align: center;
|
||
|
white-space: nowrap;
|
||
|
overflow: hidden;
|
||
|
text-overflow: ellipsis;
|
||
|
}
|
||
|
|
||
|
&__icon {
|
||
|
text-align: center;
|
||
|
font-size: 64rpx;
|
||
|
}
|
||
|
|
||
|
&__image {
|
||
|
display: block;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
&__mask {
|
||
|
position: fixed;
|
||
|
top: 0;
|
||
|
left: 0;
|
||
|
right: 0;
|
||
|
bottom: 0;
|
||
|
background-color: $tn-mask-bg-color;
|
||
|
transition: all 0.2s ease-in-out;
|
||
|
opacity: 0;
|
||
|
|
||
|
&--visible {
|
||
|
opacity: 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@keyframes fab-button-animation {
|
||
|
0% {
|
||
|
transform: scale(0.6);
|
||
|
}
|
||
|
// 20% {
|
||
|
// transform: scale(1.8);
|
||
|
// }
|
||
|
// 40% {
|
||
|
// transform: scale(0.4);
|
||
|
// }
|
||
|
// 50% {
|
||
|
// transform: scale(1.4);
|
||
|
// }
|
||
|
// 80% {
|
||
|
// transform: scale(0.8);
|
||
|
// }
|
||
|
100% {
|
||
|
transform: scale(1);
|
||
|
}
|
||
|
}
|
||
|
</style>
|