需求:
- 按钮在页面侧边悬浮显示;
- 点击按钮可展开多个快捷方式按钮,从下向上展开。
- 长按按钮,则允许拖拽来改变按钮位置,按钮为非展开状态;
- 按钮移动结束,手指松开,计算距离左右两侧距离并自动移动至侧边显示;
- 移动至侧边后,根据具体左右两次位置判断改变展开样式;
- 处理移动到非可视区域时特殊情况。
效果展示:
具体实现:
<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.currentTargetconst { 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 = falseconst selectDom = ev.currentTarget// x轴偏移量const lefts = this.oldMousePos.x - this.oldNodePos.x// y轴偏移量const tops = this.oldMousePos.y - this.oldNodePos.yconst { 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 = 0const selectDom = ev.currentTargetconst { innerWidth, innerHeight } = windowconst { offsetLeft, offsetTop } = selectDomselectDom.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>