drag-button.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <template>
  2. <view>
  3. <view
  4. id="_drag_button"
  5. class="drag"
  6. :style="'left: ' + left + 'px; top:' + top + 'px;'"
  7. @touchstart="touchstart"
  8. @touchmove.stop.prevent="touchmove"
  9. @touchend="touchend"
  10. @click.stop.prevent="click"
  11. :class="{ transition: isDock && !isMove }"
  12. >
  13. <text class="title">{{ text }}</text>
  14. <text class="price">{{ price }}</text>
  15. </view>
  16. </view>
  17. </template>
  18. <script>
  19. export default {
  20. name: "drag-button",
  21. props: {
  22. // 是否开启贴边(拖拽结束后自动贴左右边缘)
  23. isDock: {
  24. type: Boolean,
  25. default: false,
  26. },
  27. // 显示的价格数值
  28. price: {
  29. type: Number,
  30. default: 0,
  31. },
  32. // 是否存在底部TabBar(存在时需调整屏幕可用高度)
  33. existTabBar: {
  34. type: Boolean,
  35. default: false,
  36. },
  37. },
  38. data() {
  39. return {
  40. top: 0, // 按钮顶部距离屏幕顶部的距离
  41. left: 0, // 按钮左侧距离屏幕左侧的距离
  42. width: 0, // 按钮宽度
  43. height: 0, // 按钮高度
  44. offsetWidth: 0, // 按钮宽度的一半(用于拖拽时定位)
  45. offsetHeight: 0, // 按钮高度的一半(用于拖拽时定位)
  46. windowWidth: 0, // 屏幕可用宽度
  47. windowHeight: 0, // 屏幕可用高度
  48. isMove: true, // 是否处于拖拽中(控制过渡动画)
  49. edge: 15, // 按钮与屏幕边缘的间距(左右/上下通用)
  50. text: "实时金价", // 按钮顶部文本
  51. };
  52. },
  53. mounted() {
  54. // 获取系统信息(屏幕尺寸、状态栏等)
  55. const sys = uni.getSystemInfoSync();
  56. this.windowWidth = sys.windowWidth;
  57. this.windowHeight = sys.windowHeight;
  58. // APP端有TabBar时,减去TabBar高度(默认50px,可根据实际调整)
  59. // #ifdef APP-PLUS
  60. this.existTabBar && (this.windowHeight -= 50);
  61. // #endif
  62. // 处理部分设备顶部状态栏偏移(确保屏幕高度计算完整)
  63. if (sys.windowTop) {
  64. this.windowHeight += sys.windowTop;
  65. }
  66. // 获取按钮自身的尺寸(宽度/高度)
  67. const query = uni.createSelectorQuery().in(this);
  68. query
  69. .select("#_drag_button")
  70. .boundingClientRect((data) => {
  71. this.width = data.width;
  72. this.height = data.height;
  73. this.offsetWidth = data.width / 2;
  74. this.offsetHeight = data.height / 2;
  75. // 初始left:靠右显示,距离右侧边缘edge距离
  76. this.left = this.windowWidth - this.width - this.edge;
  77. // 核心修改:初始top = 屏幕高度的一半 - 按钮高度的一半(实现垂直居中)
  78. // 确保按钮的垂直中心与屏幕中心对齐,而非按钮顶部对齐屏幕中心
  79. this.top = this.windowHeight / 2 - this.height / 2;
  80. })
  81. .exec();
  82. },
  83. methods: {
  84. // 按钮点击事件(向父组件传参)
  85. click() {
  86. this.$emit("btnClick");
  87. },
  88. // 触摸开始事件(向父组件传参)
  89. touchstart(e) {
  90. this.$emit("btnTouchstart");
  91. },
  92. // 触摸移动事件(控制按钮拖拽位置)
  93. touchmove(e) {
  94. // 仅允许单指拖拽
  95. if (e.touches.length !== 1) {
  96. return false;
  97. }
  98. this.isMove = true; // 拖拽中关闭过渡动画
  99. // 水平方向定位:触摸点X坐标 - 按钮宽度的一半(确保触摸点在按钮中心)
  100. this.left = e.touches[0].clientX - this.offsetWidth;
  101. // 垂直方向定位:触摸点Y坐标 - 按钮高度的一半
  102. let clientY = e.touches[0].clientY - this.offsetHeight;
  103. // H5端特殊处理:补偿高度偏移(避免拖拽时位置偏移)
  104. // #ifdef H5
  105. clientY += this.height;
  106. // #endif
  107. // 计算垂直方向边界:底部最大可移动距离(避免按钮超出屏幕底部)
  108. let edgeBottom = this.windowHeight - this.height - this.edge;
  109. // 垂直方向边界限制:不允许超出屏幕上下边缘
  110. if (clientY < this.edge) {
  111. this.top = this.edge; // 上边界
  112. } else if (clientY > edgeBottom) {
  113. this.top = edgeBottom; // 下边界
  114. } else {
  115. this.top = clientY; // 正常拖拽位置
  116. }
  117. },
  118. // 触摸结束事件(控制贴边逻辑 + 向父组件传参)
  119. touchend(e) {
  120. // 开启贴边功能时,拖拽结束后自动贴左右边缘
  121. if (this.isDock) {
  122. const edgeRight = this.windowWidth - this.width - this.edge; // 右侧边缘位置
  123. // 判断按钮中心是否在屏幕左半区:是则贴左,否则贴右
  124. if (this.left < this.windowWidth / 2 - this.offsetWidth) {
  125. this.left = this.edge; // 贴左边缘
  126. } else {
  127. this.left = edgeRight; // 贴右边缘
  128. }
  129. }
  130. this.isMove = false; // 拖拽结束开启过渡动画
  131. this.$emit("btnTouchend"); // 向父组件传参
  132. },
  133. },
  134. };
  135. </script>
  136. <style lang="scss">
  137. .drag {
  138. display: flex;
  139. justify-content: center;
  140. align-items: center;
  141. flex-direction: column;
  142. background: linear-gradient(180deg, #fefcf9 0%, #fff2df 100%);
  143. box-shadow: 0rpx 3rpx 12rpx 0rpx rgba(0, 0, 0, 0.16);
  144. width: 105upx;
  145. height: 105upx;
  146. border-radius: 50%; // 圆形按钮
  147. position: fixed; // 固定定位(不随页面滚动)
  148. z-index: 999999; // 最高层级,避免被其他元素遮挡
  149. .title {
  150. font-size: 19rpx;
  151. color: #000000;
  152. margin-bottom: 4rpx; // 文本与价格间距
  153. }
  154. .price {
  155. font-size: 26rpx;
  156. color: #cc0000;
  157. font-weight: bold;
  158. }
  159. // 贴边/停止拖拽时的过渡动画
  160. &.transition {
  161. transition: left 0.3s ease, top 0.3s ease;
  162. }
  163. }
  164. </style>