A008_拖拽移动悬浮球

参考链接:vue 拖拽移动(类似于iPhone虚拟home )Vue实现仿iPhone悬浮球

需求拆解

  • 1.元素悬浮
  • 2.元素可拖拽
  • 3.元素拖拽结束后会吸附贴壁
  • 4.元素单击唤出菜单
  • 5.菜单展开时点击空白处关闭菜单
  • 6.菜单不可拖拽
  • 7.元素拖拽时菜单不打开

实现思路

  • 1.鼠标按下时
  • 1.1.如果打开菜单,则不做响应
  • 1.2.未打开菜单,记录 按下状态 记录x y轴坐标
  • 2.按下移动时 动态计算坐标 设置拖拽元素 style 控制位置 ;
  • 2.1.判断拖拽区域 溢出时 归位判断;
  • 2.2.拖拽时 阻止页面滑动
  • 2.3.增加拖拽计数
  • 3.鼠标抬起
  • 3.1.修改 按下状态
  • 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 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(){}
        }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  .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;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

感悟