组件代码/components/float-bubble.vue
<template>
<transition>
<div
ref="dragArea"
class="drag-area"
@touchstart.stop="handleTouchStart"
@touchmove.prevent.stop="handleTouchMove($event)"
@touchend.stop="handleTouchEnd"
:style="{ left: `${left}px`, top: `${top}px`, width: `${slotWidth}px`, height: `${slotHeight}px` }"
v-if="isShow"
>
<slot></slot>
</div>
</transition>
</template>
<script>
export default {
props: {
slotElemClass: {
//slot包裹元素类名,必传,用以获取其宽高时使用,如<FloatBubble slotElemClass=".float-btn">...</FloatBubble>
type: String,
required: true,
},
isAdsorb: {
//是否开启自动吸附侧边
type: Boolean,
default: true,
},
adsorbType: {
//吸附侧边类型:right-仅吸附右边,left-仅吸附左边,double-自适应吸附两边
type: String,
default: 'double',
},
initTop: {
//元素初始top值,不设置默认居中
type: Number,
},
initLeft: {
//元素初始left值,不设置默认100%,即在最右侧
type: Number,
},
},
data() {
return {
slotWidth: '', //插槽元素宽度,因使用了rem缩放,所以这里是动态计算得来的
slotHeight: '', //插槽元素高度
disdanceEdge: 0, //吸附后距离吸附边的间距,默认为0,即贴边吸附
left: 0,
top: 0,
startToMove: false,
isShow: true,
timer: null,
currentTop: null,
clientW: document.documentElement.clientWidth, //视口宽
clientH: document.documentElement.clientHeight, //视口高
};
},
mounted() {
this.slotWidth = document.querySelector(this.slotElemClass).clientWidth;
this.slotHeight = document.querySelector(this.slotElemClass).clientHeight;
this.left = this.initLeft || this.clientW - this.slotWidth - this.disdanceEdge; //初始化默认left为右贴边
this.top = this.initTop || this.clientH / 2 - this.slotHeight / 2; //初始化默认top为居中
this.bindScrollEvent();
},
beforeDestroy() {
// 记得销毁一些全局的的事件
this.removeScrollEvent();
},
methods: {
handleTouchStart() {
this.startToMove = true;
this.$refs.dragArea.style.transition = 'none';
},
handleTouchMove(e) {
const clientX = e.targetTouches[0].clientX; //手指相对视口的x
const clientY = e.targetTouches[0].clientY; //手指相对视口的y
const isInScreen = clientX <= this.clientW && clientX >= 0 && clientY <= this.clientH && clientY >= 0;
if (this.startToMove && e.targetTouches.length === 1) {
if (isInScreen) {
this.left = clientX - this.slotWidth / 2;
this.top = clientY - this.slotHeight / 2;
}
}
},
handleTouchEnd() {
if (this.left < this.clientW / 2) {
if (this.isAdsorb) {
if (['double', 'left'].includes(this.adsorbType)) {
this.left = this.disdanceEdge;
} else {
this.left = this.clientW - this.slotWidth - this.disdanceEdge;
}
}
this.handleIconY();
} else {
if (this.isAdsorb) {
if (['double', 'right'].includes(this.adsorbType)) {
this.left = this.clientW - this.slotWidth - this.disdanceEdge;
} else {
this.left = this.disdanceEdge;
}
}
this.handleIconY();
}
this.$refs.dragArea.style.transition = 'all .3s';
},
handleIconY() {
if (this.top < 0) {
this.top = this.disdanceEdge; //贴边距离
} else if (this.top + this.slotHeight > this.clientH) {
this.top = this.clientH - this.slotHeight - this.disdanceEdge;
}
},
bindScrollEvent() {
window.addEventListener('scroll', this.handleScrollStart);
},
removeScrollEvent() {
window.removeEventListener('scroll', this.handleScrollStart);
},
handleScrollStart() {
// this.isShow = false;
this.timer && clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.handleScrollEnd();
}, 300);
this.currentTop = document.documentElement.scrollTop || document.body.scrollTop;
},
handleScrollEnd() {
this.scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 判断是否停止滚动的条件
if (this.scrollTop == this.currentTop) {
this.isShow = true;
}
},
},
};
</script>
<style scoped lang="less">
.drag-area {
position: fixed;
z-index: 800;
}
.v-enter {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.3s;
}
</style>
如何使用
import FloatBubble from '@/components/.../float-bubble.vue';
components: {
FloatBubble,
},
<FloatBubble slotElemClass=".float-btn">
<div class="float-btn">按钮</div>
</FloatBubble>