拖拽移动悬浮球
参考链接:vue 拖拽移动(类似于iPhone虚拟home )
、Vue实现仿iPhone悬浮球
需求拆解
- 1.元素悬浮于全屏
- 2.元素可拖拽
- 3.元素拖拽结束后会吸附贴壁
- 4.元素单击唤出菜单
- 5.菜单展开时点击空白处关闭菜单
- 6.菜单不可拖拽
- 7.元素拖拽时菜单不打开
- 8.元素拖拽至接近底部时,若剩余高度不足以展开菜单则自动吸附至底部。
实现思路
如果适配移动端,则需加注touch
事件
- 1.鼠标按下时
- 1.1.如果此时已打开菜单,则不做响应
- 1.2.未打开菜单,记录 按下状态为true 记录x y轴坐标
- 2.按下移动时 动态计算坐标 设置拖拽元素 style 控制位置 ;
- 2.1.判断拖拽区域 溢出时 归位判断;
- 2.2.拖拽时 阻止页面滑动
- 2.3.增加拖拽计数
- 3.鼠标抬起
- 3.1.修改 按下状态 为false
- 3.2.根据元素位置和容器高宽动态计算元素最后应该吸附位置
- 4.元素点击事件
- 4.1.拖拽计数与历史计数差小于10则执行点击事件
- 4.2.反之不执行
实现过程
<template>
<!-- 全屏容器 -->
<div ref="pageDiv" @mousemove="demo_move" @mouseup="demo_up"
:class="{'zlevelTop':mouseDownState}"
style="position: absolute;top: 0;height: 100%;width: 100%">
<!-- 点击蒙版 -->
<div v-if="menuOpen" @click.stop="closeOpenModal"
style="position: fixed;top: 0;left: 0;width: 100%;height: 100%;z-index: 998"></div>
<!-- 多功能菜单 -->
<div :class="{'six-more-modal-btn':menuOpen,'moreModal':!menuOpen,'more-tran-animate':!mouseDownState}"
ref="actionMgr" :style="position" @mousedown="demo_down">
<!-- 触发器 -->
<div v-if="!menuOpen" @click="demo_click" class="imgMore">
<img class="more-img" :src="" alt="" title="多功能菜单"/>
</div>
<!-- 菜单 -->
<div v-else></div>
</div>
</div>
</template>
export default {
name: "homeDragbtn",
props: {
// 通过position来设置初始定位
position: {
type: Object,
default: function() {
return {
top: "32.25rem",
left: "0"
}
}
}
},
data() {
return {
menuOpen:false, // 菜单展开状态
mouseDownState:false, // 鼠标点击状态
iX:0, iY:0,
dX:0, dY:500, // 初始定位
lastMoveIndex:0, // 拖拽计数
curMoveIndex:0, // 历史计数
}
},
methods: {
// 鼠标按下
demo_down(event){
// 如果打开了菜单,则不做响应
if(this.menuOpen){
this.mouseDownState = false;
return
}
console.log("demo_down",event);
/* 此处判断 pc 或 移动端 得到 event 事件 */
var touch;
if (event.touches) {
touch = event.touches[0];
} else {
touch = event;
}
// 鼠标点击 面向页面 的 x坐标 y坐标
let { clientX, clientY } = touch;
// 鼠标x坐标 - 拖拽按钮x坐标 得到鼠标 距离 拖拽按钮 的间距
this.iX = clientX - this.$refs.actionMgr.offsetLeft;
// 鼠标y坐标 - 拖拽按钮y坐标 得到鼠标 距离 拖拽按钮 的间距
this.iY = clientY - this.$refs.actionMgr.offsetTop;
// 设置当前 状态为 鼠标按下
this.mouseDownState = true;
},
// 鼠标拖拽
demo_move(event){
//鼠标按下 切移动中
if (this.mouseDownState) {
console.log("demo_move",event);
/* 此处判断 pc 或 移动端 得到 event 事件 */
var touch;
if (event.touches) {
touch = event.touches[0];
} else {
touch = event;
}
// 鼠标移动时 面向页面 的 x坐标 y坐标
let { clientX, clientY } = touch;
//当前页面全局容器 dom 元素 获取容器 宽高
let {
clientHeight: pageDivY,
clientWidth: pageDivX
} = this.$refs.pageDiv;
/* 鼠标坐标 - 鼠标与拖拽按钮的 间距坐标 得到 拖拽按钮的 左上角 x轴y轴坐标 */
let [x, y] = [clientX - this.iX, clientY - this.iY];
//拖拽按钮 dom 元素 获取 宽高 style 对象
let {
clientHeight: actionMgrY,
clientWidth: actionMgrX,
style: actionMgrStyle
} = this.$refs.actionMgr;
/* 此处判断 拖拽按钮 如果超出 屏幕宽高 或者 小于
设置 屏幕最大 x=全局容器x y=全局容器y 否则 设置 为 x=0 y=0
*/
if (x > pageDivX - actionMgrX) x = pageDivX - actionMgrX;
else if (x < 0) x = 0;
if (y > pageDivY - actionMgrY) y = pageDivY - actionMgrY;
else if (y < 0) y = 0;
this.dX =x;this.dY = y;
// 计算后坐标 设置 按钮位置
actionMgrStyle.left = `${x}px`;
actionMgrStyle.top = `${y}px`;
actionMgrStyle.bottom = "auto";
actionMgrStyle.right = "auto";
// move Index
this.lastMoveIndex++;
// 当按下键滑动时, 阻止屏幕滑动事件
event.preventDefault();
}
},
// 鼠标抬起
demo_up(event){
console.log("demo_up",event);
// 当前页面全局容器 dom 元素 获取容器 宽高
let {
clientHeight: windowHeight,
clientWidth: windowWidth
} = document.documentElement;
console.log('全局容器:',windowWidth,windowHeight);
// 拖拽按钮 dom 元素 获取 宽高 style 对象
let {
clientHeight: actionMgrY,
clientWidth: actionMgrX,
style: actionMgrStyle
} = this.$refs.actionMgr;
console.log('拖拽按钮',actionMgrY,actionMgrX,actionMgrStyle);
// 计算后坐标 设置 按钮位置
if(this.dY>0&&this.dY<(windowHeight-50)){ // 不在顶部 且 不在底部
if(this.dX<=(windowWidth/2)){ // left 小于等于屏幕一半
actionMgrStyle.left = 0;
actionMgrStyle.right = 'auto';
}else { // left 大于屏幕一半
actionMgrStyle.left = 'auto';
actionMgrStyle.right = 0;
}
if(this.dY>=(windowHeight/2)){ // 宽度大于1/2时,是将top改为auto,调整bottom
actionMgrStyle.top = 'auto';
actionMgrStyle.bottom = (windowHeight - this.dY - 50) + 'px';
}
}else {
if(this.dY===0){ // 在顶部
actionMgrStyle.top = 0;
actionMgrStyle.bottom = 'auto';
}else if(this.dY===(windowHeight-50)){
actionMgrStyle.bottom = 0;
actionMgrStyle.top = 'auto';
}
if(this.dX>=(windowWidth/2)){ // 右侧是将left改为auto,调整right
actionMgrStyle.left = 'auto';
actionMgrStyle.right = (windowWidth - this.dX - 50) + 'px';
}
}
this.mouseDownState = false;
},
// 单击事件
demo_click(){
console.log("demo_click|moveIndex:",this.lastMoveIndex,this.curMoveIndex);
// mouseup 后会激活click事件
// 如果上一次down事件到下一次click事件中经历10次以下move,则视为纯点击事件
if(this.lastMoveIndex-this.curMoveIndex<=10){
// 点击事件
this.menuOpen = !this.menuOpen;
if( this.menuOpen ){
// 打开菜单
}
}
this.curMoveIndex = this.lastMoveIndex
},
// 点击空白关闭菜单
closeOpenModal(){}
}
}
.zlevelTop{
z-index: 9999;
}
.more-tran-animate{
transition:0.5s;
}
.moreModal {
/* 如果碰到滑动问题,1.3 请检查 z-index。z-index需比web大一级*/
z-index: 9999;
position: fixed;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #337AB7;
line-height: 40px;
text-align: center;
color: #fff;
opacity: 0.6;
}
.moreModal:hover{
opacity: 1;
}
.six-more-modal-btn{
position: fixed;
z-index: 9999;
width: 14rem;
height: 14rem;
border-radius: 5px;
background: #1A1A1A;
color: #fff;
}
.imgMore{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。