m-drag-vue2.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <template>
  2. <scroll-view class="m-drag" scroll-y :style="{ height: itemHeight * newList.length + 'px' }">
  3. <view
  4. v-for="(item, index) in newList"
  5. :key="index"
  6. class="m-drag-item"
  7. :class="{ active: currentIndex === index }"
  8. :style="{
  9. top: itemYList[index].top + 'px'
  10. }"
  11. >
  12. <slot :item="item" />
  13. <view class="icon" @touchstart="touchStart($event, index)" @touchmove="touchMove" @touchend="touchEnd">
  14. <i class="lines" />
  15. </view>
  16. </view>
  17. </scroll-view>
  18. </template>
  19. <script>
  20. export default {
  21. props: {
  22. // 每一项item高度
  23. itemHeight: {
  24. type: Number,
  25. required: true
  26. },
  27. // 数据列表
  28. list: {
  29. type: Array,
  30. required: true
  31. },
  32. // 是否只读
  33. readonly: {
  34. type: Boolean,
  35. default: false
  36. }
  37. },
  38. data() {
  39. return {
  40. // 数据
  41. newList: [],
  42. // 记录所有item的初始坐标
  43. initialItemYList: [],
  44. // 坐标数据
  45. itemYList: [],
  46. // 记录当前手指的垂直方向的坐标
  47. touchY: 0,
  48. // 记录当前操作的item数据
  49. currentItemY: {},
  50. // 当前操作的item的下标
  51. currentIndex: -1
  52. }
  53. },
  54. watch: {
  55. list: {
  56. handler(val) {
  57. if (!val?.length) return
  58. // 获取数据列表
  59. this.newList = val
  60. // 获取所有item的初始坐标
  61. this.initialItemYList = this.getItemsY()
  62. // 初始化坐标
  63. this.itemYList = this.getItemsY()
  64. },
  65. immediate: true
  66. }
  67. },
  68. created() {},
  69. methods: {
  70. /** @初始化各个item的坐标 **/
  71. getItemsY() {
  72. return this.list.map((item, i) => {
  73. return {
  74. left: 0,
  75. top: i * this.itemHeight
  76. }
  77. })
  78. },
  79. /** @开始触摸 */
  80. touchStart(event, index) {
  81. // 只读
  82. if (this.readonly) return
  83. // H5拖拽时,禁止触发ios回弹
  84. this.h5BodyScroll(false)
  85. const [{ pageY }] = event.touches
  86. // 记录数据
  87. this.currentIndex = index
  88. this.touchY = pageY
  89. this.currentItemY = this.itemYList[index]
  90. },
  91. /** @手指滑动 **/
  92. touchMove(event) {
  93. // 只读
  94. if (this.readonly) return
  95. const [{ pageY }] = event.touches
  96. const current = this.itemYList[this.currentIndex]
  97. const prep = this.itemYList[this.currentIndex - 1]
  98. const next = this.itemYList[this.currentIndex + 1]
  99. // 获取移动差值
  100. this.itemYList[this.currentIndex] = {
  101. top: current.top + (pageY - this.touchY)
  102. }
  103. // 记录手指坐标
  104. this.touchY = pageY
  105. // 向下移动(超过下一个的1/2就进行换位)
  106. if (next && current.top > next.top - this.itemHeight / 2) {
  107. this.changePosition(this.currentIndex + 1)
  108. } else if (prep && current.top < prep.top + this.itemHeight / 2) {
  109. // 向上移动(超过上一个的1/2就进行换位)
  110. this.changePosition(this.currentIndex - 1)
  111. }
  112. },
  113. /** @手指松开 */
  114. touchEnd() {
  115. // 只读
  116. if (this.readonly) return
  117. // 传给父组件新数据
  118. this.$emit('change', this.newList, this.newList[this.currentIndex])
  119. // 将拖拽的item归位
  120. this.itemYList[this.currentIndex] = this.initialItemYList[this.currentIndex]
  121. this.currentIndex = -1
  122. // H5开启ios回弹
  123. this.h5BodyScroll(true)
  124. },
  125. /** @交换位置 **/
  126. // index 需要与第几个下标交换位置
  127. changePosition(index) {
  128. // 记录当前拖拽的item数据
  129. const tempItem = this.newList[this.currentIndex]
  130. // 设置原来位置的item
  131. this.newList[this.currentIndex] = this.newList[index]
  132. // 将临时存放的数据设置好
  133. this.newList[index] = tempItem
  134. // 调整位置item
  135. this.itemYList[index] = this.itemYList[this.currentIndex]
  136. this.itemYList[this.currentIndex] = this.currentItemY
  137. // 改变当前操作的的下标
  138. this.currentIndex = index
  139. // 记录新位置的数据
  140. this.currentItemY = this.initialItemYList[this.currentIndex]
  141. },
  142. // h5 ios回弹
  143. h5BodyScroll(flag) {
  144. // #ifdef H5
  145. document.body.style.overflow = flag ? 'initial' : 'hidden'
  146. // #endif
  147. }
  148. }
  149. }
  150. </script>
  151. <style scoped lang="scss">
  152. .m-drag {
  153. position: relative;
  154. width: 100%;
  155. ::-webkit-scrollbar {
  156. display: none;
  157. }
  158. .m-drag-item {
  159. position: absolute;
  160. left: 0;
  161. right: 0;
  162. transition: all ease 0.25s;
  163. display: flex;
  164. align-items: center;
  165. > :deep(view:not(.icon)) {
  166. flex: 1;
  167. }
  168. .icon {
  169. padding: 30rpx;
  170. .lines {
  171. background: #e0e0e0;
  172. width: 20px;
  173. height: 2px;
  174. border-radius: 100rpx;
  175. margin-left: auto;
  176. position: relative;
  177. display: block;
  178. transition: all ease 0.25s;
  179. &::before,
  180. &::after {
  181. position: absolute;
  182. width: inherit;
  183. height: inherit;
  184. border-radius: inherit;
  185. background: #e0e0e0;
  186. transition: inherit;
  187. content: '';
  188. display: block;
  189. }
  190. &::before {
  191. top: -14rpx;
  192. }
  193. &::after {
  194. bottom: -14rpx;
  195. }
  196. }
  197. }
  198. // 拖拽中的元素,添加阴影、关闭动画、层级置顶
  199. &.active {
  200. box-shadow: 0 0 14rpx rgba(0, 0, 0, 0.08);
  201. transition: initial;
  202. z-index: 1;
  203. .icon .lines {
  204. background: #2e97f9;
  205. &::before,
  206. &::after {
  207. background: #2e97f9;
  208. }
  209. }
  210. }
  211. }
  212. }
  213. </style>