vue 悬浮框 可拖动
效果图:

首先是样式布局:
<div class="ys-float-btn" :style="{'width':itemWidth+'px','height':itemHeight+'px','left':left+'px','top':top+'px'}" ref="div" @click ="onBtnClicked"> <slot name="icon"></slot> <image class="su_img" src="../../../static/clouddisk/icon_qiye.svg"></image> </div>
第二步首次进入页面时,按钮应该处于一个初始位置。我们在created钩子中进行初始化。
created(){ this.left = document.documentElement.clientWidth - 50; this.top = document.documentElement.clientHeight*0.8; },
第三步,在写一个监听事件
mounted(){ window.addEventListener('scroll', this.handleScrollStart); this.$nextTick(()=>{ const div = this.$refs.div; div.addEventListener("touchstart",()=>{ div.style.transition = 'none'; }); div.addEventListener("touchmove",(e)=>{ if (e.targetTouches.length === 1) { let touch = event.targetTouches[0]; this.left = touch.clientX - this.itemWidth/2; this.top = touch.clientY - this.itemHeight/2; } }); div.addEventListener("touchend",()=>{ div.style.transition = 'all 0.3s'; if(this.left>this.clientWidth/2){ this.left = this.clientWidth - this.itemWidth - this.gapWidth; }else{ this.left = this.gapWidth; } }); }); },
第四步,需要在popos里面定义初始值:
props:{ itemWidth:{ type:Number, default:60 }, itemHeight:{ type:Number, default:60 }, gapWidth:{ type:Number, default:10 }, coefficientHeight:{ type:Number, default:0.8 } },
第五步在对methods进行处理:
methods:{ onBtnClicked(){ this.$emit("onFloatBtnClicked"); }, },
第六步在将created里面拖动的left和top进行处理,完整代码是这样子的:
created(){ this.clientWidth = document.documentElement.clientWidth; this.clientHeight = document.documentElement.clientHeight; this.left = this.clientWidth - this.itemWidth - this.gapWidth; this.top = this.clientHeight*this.coefficientHeight; },
最后补充一点,我这边因为需要,我将拖动的上下距离在mounted里面做了判断,让他拖动的时候不会超出我的头部的高度和底部的高度
mounted(){
this.$nextTick(()=>{
const div = this.$refs.div;
div.addEventListener("touchstart",(e)=>{
e.stopPropagation();
div.style.transition = 'none';
});
div.addEventListener("touchmove",(e)=>{
e.stopPropagation();
if (e.targetTouches.length === 1) {
let touch = event.targetTouches[0];
this.left = touch.clientX - this.itemWidth/2;
this.top = touch.clientY - this.itemHeight/2;
}
},
false
);
div.addEventListener("touchend",(e)=>{
e.stopPropagation();
div.style.transition = 'all 0.3s';
if(this.left>this.clientWidth/2){
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
}else{
this.left = this.gapWidth;
}
if(this.top<=36)
{
this.top=36+this.gapWidth
}
else{
let bottom=this.clientHeight-50-this.itemHeight-this.gapWidth
console.log(bottom,this.top)
if(this.top>=bottom)
{
this.top=bottom
}
}
});
});
},
如果你们没有头部和底部高度可以随意滚动就直接这样子写就行:
mounted(){ window.addEventListener('scroll', this.handleScrollStart); this.$nextTick(()=>{ const div = this.$refs.div; div.addEventListener("touchstart",()=>{ div.style.transition = 'none'; }); div.addEventListener("touchmove",(e)=>{ if (e.targetTouches.length === 1) { let touch = event.targetTouches[0]; this.left = touch.clientX - this.itemWidth/2; this.top = touch.clientY - this.itemHeight/2; } }); div.addEventListener("touchend",()=>{ div.style.transition = 'all 0.3s'; if(this.left>this.clientWidth/2){ this.left = this.clientWidth - this.itemWidth - this.gapWidth; }else{ this.left = this.gapWidth; } }); }); },
最后我直接就完整代码:
<template>
<div class="ys-float-btn" :style="{'width':itemWidth+'px','height':itemHeight+'px','left':left+'px','top':top+'px'}"
ref="div"
@click ="onBtnClicked">
<slot name="icon"></slot>
<image class="su_img" src="../../../static/clouddisk/icon_qiye.svg"></image>
</div>
</template>
<script>
export default {
name: "FloatImgBtn",
props:{
itemWidth:{
type:Number,
default:60
},
itemHeight:{
type:Number,
default:60
},
gapWidth:{
type:Number,
default:10
},
coefficientHeight:{
type:Number,
default:0.8
}
},
created(){
this.clientWidth = document.documentElement.clientWidth;
this.clientHeight = document.documentElement.clientHeight;
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
this.top = this.clientHeight*this.coefficientHeight;
},
mounted(){
this.$nextTick(()=>{
const div = this.$refs.div;
div.addEventListener("touchstart",(e)=>{
e.stopPropagation();
div.style.transition = 'none';
});
div.addEventListener("touchmove",(e)=>{
e.stopPropagation();
if (e.targetTouches.length === 1) {
let touch = event.targetTouches[0];
this.left = touch.clientX - this.itemWidth/2;
this.top = touch.clientY - this.itemHeight/2;
}
},
false
);
div.addEventListener("touchend",(e)=>{
e.stopPropagation();
div.style.transition = 'all 0.3s';
if(this.left>this.clientWidth/2){
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
}else{
this.left = this.gapWidth;
}
if(this.top<=36)
{
this.top=36+this.gapWidth
}
else{
let bottom=this.clientHeight-50-this.itemHeight-this.gapWidth
console.log(bottom,this.top)
if(this.top>=bottom)
{
this.top=bottom
}
}
});
});
},
methods:{
onBtnClicked(){
this.$emit("onFloatBtnClicked");
},
},
data(){
return{
timer:null,
currentTop:0,
clientWidth:0,
clientHeight:0,
left:0,
top:0,
}
}
}
</script>
<style lang="scss" scoped>
.ys-float-btn{
background:rgba(56,181,77,1);
box-shadow:0 2px 10px 0 rgba(0,0,0,0.1);
border-radius:50%;
color: #666666;
z-index: 20;
transition: all 0.3s;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: fixed;
bottom: 20vw;
img{
width: 50%;
height: 50%;
object-fit: contain;
margin-bottom: 3px;
}
}
.su_img{
width: 40px;
height: 40px;
margin: 8px 0 0 0;
}
</style>

浙公网安备 33010602011771号