组件代码/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: {
type: String,
required: true,
},
isAdsorb: {
type: Boolean,
default: true,
},
adsorbType: {
type: String,
default: 'double',
},
initTop: {
type: Number,
},
initLeft: {
type: Number,
},
},
data() {
return {
slotWidth: '',
slotHeight: '',
disdanceEdge: 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;
this.top = this.initTop || this.clientH / 2 - this.slotHeight / 2;
this.bindScrollEvent();
},
beforeDestroy() {
this.removeScrollEvent();
},
methods: {
handleTouchStart() {
this.startToMove = true;
this.$refs.dragArea.style.transition = 'none';
},
handleTouchMove(e) {
const clientX = e.targetTouches[0].clientX;
const clientY = e.targetTouches[0].clientY;
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.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>
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步