需求:
- 按钮在页面侧边悬浮显示;
- 点击按钮可展开多个快捷方式按钮,从下向上展开。
- 长按按钮,则允许拖拽来改变按钮位置,按钮为非展开状态;
- 按钮移动结束,手指松开,计算距离左右两侧距离并自动移动至侧边显示;
- 移动至侧边后,根据具体左右两次位置判断改变展开样式;
- 处理移动到非可视区域时特殊情况。
效果展示:
具体实现:
<template>
<div class="shortcut" @touchstart="touchstart($event)" @touchmove="touchMove($event)" @touchend="touchEnd($event)" v-if="isMobile">
<div class="shortcut__container">
<transition name="fade">
<div class="shadow" v-if="showPopover" @click.stop="showPopover = false"></div>
</transition>
<transition name="sub-fade">
<div :class="['shortcut__list', `${type}`]" v-if="showPopover">
<div class="shortcut__list_item">
<div class="icon-box"><img src="@/images/common/ic_question.png" alt=""></div>
投资理财
</div>
<div class="shortcut__list_item">
<div class="icon-box"><img src="@/images/common/ic_question.png" alt=""></div>
我的资产
</div>
<div class="shortcut__list_item">
<div class="icon-box"><img src="@/images/common/ic_question.png" alt=""></div>
咨询我们
</div>
</div>
</transition>
<div class="shortcut__btn" :class="{ anim: showPopover }" @click.stop="handleBtn()">+</div>
</div>
</div>
</template>
<script>
const TIME = 50
export default {
data () {
return {
isMobile: /Mobi|Android|iPhone/i.test(navigator.userAgent),
showPopover: false,
timeOutEvent: 0,
longClick: 0,
// 手指原始位置
oldMousePos: {},
// 元素原始位置
oldNodePos: {},
type: 'right',
}
},
methods: {
touchstart (ev) {
// 定时器控制长按时间,超过{TIME}毫秒开始进行拖拽
this.timeOutEvent = setTimeout(() => {
this.longClick = 1
}, TIME)
const selectDom = ev.currentTarget
const { pageX, pageY } = ev.touches[0] // 手指位置
const { offsetLeft, offsetTop } = selectDom // 元素位置
// 手指原始位置
this.oldMousePos = {
x: pageX,
y: pageY,
}
// 元素原始位置
this.oldNodePos = {
x: offsetLeft,
y: offsetTop,
}
this.handleMoving()
selectDom.style.left = `${offsetLeft}px`
selectDom.style.top = `${offsetTop}px`
},
touchMove (ev) {
// 未达到{TIME}毫秒就移动则不触发长按,清空定时器
clearTimeout(this.timeOutEvent)
if (this.longClick === 1) {
this.handleMoving()
this.showPopover = false
const selectDom = ev.currentTarget
// x轴偏移量
const lefts = this.oldMousePos.x - this.oldNodePos.x
// y轴偏移量
const tops = this.oldMousePos.y - this.oldNodePos.y
const { pageX, pageY } = ev.touches[0] // 手指位置
selectDom.style.left = `${pageX - lefts}px`
selectDom.style.top = `${pageY - tops}px`
}
},
touchEnd (ev) {
// 清空定时器
clearTimeout(this.timeOutEvent)
if (this.longClick === 1) {
this.longClick = 0
const selectDom = ev.currentTarget
const { innerWidth, innerHeight } = window
const { offsetLeft, offsetTop } = selectDom
selectDom.style.left = offsetLeft + 50 > innerWidth / 2 ? 'calc(100% - 55px)' : '15px'
if (offsetTop < 150) {
selectDom.style.top = '150px'
} else if (offsetTop + 150 > innerHeight) {
selectDom.style.top = `${innerHeight - 150}px`
}
this.type = offsetLeft + 50 > innerWidth / 2 ? 'right' : 'left'
setTimeout(() => {
document.body.style.overflow = 'auto'
document.body.style.userSelect = 'auto'
}, 1000)
}
},
handleMoving () {
// 禁止body滚动
document.body.style.overflow = 'hidden'
// 禁止body文本选中
document.body.style.userSelect = 'none'
},
handleBtn () {
this.showPopover = !this.showPopover
}
},
}
</script>
<style scoped lang="less">
.icon-box {
background: #fff;
width: .8rem;
height: .8rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.shortcut {
position: fixed;
z-index: 9999;
left: calc(100% - 55px);
top: calc(100% - 150px);
user-select: none;
&__container {
position: relative;
}
&__list {
position: absolute;
bottom: .8rem;
z-index: 8;
&_item {
color: #fff;
display: flex;
flex-direction: row;
align-items: center;
white-space: nowrap;
margin-bottom: .15rem;
.icon-box {
margin: 0 .1rem 0 0;
img {
width: 0.36rem;
height: 0.36rem;
}
}
}
&.left {
left: 0;
}
&.right {
right: 0;
.shortcut__list_item {
flex-direction: row-reverse;
.icon-box {
margin: 0 0 0 .1rem;
}
}
}
}
&__btn {
background: #fff;
width: .8rem;
height: .8rem;
border-radius: 50%;
text-align: center;
line-height: .7rem;
color: #3356D9;
font-size: .5rem;
position: relative;
z-index: 8;
border: 1px solid #3356D9;
transition: all .3s linear;
&.anim {
transform: rotate(135deg);
}
}
}
.shadow {
width: 100%;
max-width: 1024px;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1;
margin: 0 auto;
}
.sub-fade-leave-active,.sub-fade-enter-active {
transition: max-height 0.3s linear;
}
.sub-fade-enter,.sub-fade-leave-to {
max-height: 0;
overflow: hidden;
}
.sub-fade-enter-to,.sub-fade-leave {
max-height: 2.56rem;
overflow: hidden;
}
</style>