详情小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

325 lines
8.6 KiB

  1. <template>
  2. <view>
  3. <view :style="{'overflow-x': overOnePage ? 'hidden' : 'initial'}">
  4. <view class="item-wrap" :style="{'height': itemWrapHeight +'px'}">
  5. <view class="item"
  6. :class="{'cur': cur == index, 'zIndex': curZ == index, 'itemTransition': itemTransition}"
  7. v-for="(item, index) in list"
  8. :key="index"
  9. :id="'item'+index"
  10. :data-key="item.key"
  11. :data-index="index"
  12. :style="[itemStyle(index, item, item.key)]"
  13. @longpress="longPress"
  14. @touchmove.stop="touchMove"
  15. @touchend.stop="touchEnd">
  16. <view class="info">
  17. <view>
  18. <image :src="item.data"></image>
  19. </view>
  20. </view>
  21. </view>
  22. </view>
  23. </view>
  24. <view v-if="overOnePage" class="indicator">
  25. <view>滑动此区域滚动页面</view>
  26. </view>
  27. </view>
  28. </template>
  29. <script>
  30. export default {
  31. props: {
  32. },
  33. data(){
  34. return {
  35. touch: false,
  36. startX: 0,
  37. startY: 0,
  38. columns: 3,
  39. item: {},
  40. tranX: 0,
  41. tranConstX: 0,
  42. itemWrap: {},
  43. tranY: 0,
  44. teanConstY: 0,
  45. overOnePage: false,
  46. windowHeight: 0,
  47. originKey: null,
  48. list: [],
  49. cur: -1,
  50. curZ: -1,
  51. listData: [
  52. 'https://picsum.photos/200',
  53. 'https://picsum.photos/200',
  54. 'https://picsum.photos/200',
  55. 'https://picsum.photos/200',
  56. 'https://picsum.photos/200/300'],
  57. itemWrapHeight: 0
  58. }
  59. },
  60. computed: {
  61. itemStyle(){
  62. let that = this;
  63. return function(index, item, key){
  64. let style_obj = {
  65. width: Math.floor(100 / that.columns) +'%',
  66. 'margin-top': Math.ceil((key+1) / that.columns) * 5 +'px'
  67. }
  68. let str = 'translate3d(';
  69. if(index === that.cur){
  70. str += that.tranX +'px';
  71. str += ', ';
  72. str += that.tranY +'px';
  73. }else{
  74. str += item.tranX +'px';
  75. str += ', ';
  76. str += item.tranY +'px';
  77. }
  78. str += ', 0px)';
  79. style_obj.transform = str;
  80. return style_obj;
  81. }
  82. }
  83. },
  84. mounted(){
  85. this.init();
  86. },
  87. methods: {
  88. // 长按触发移动排序
  89. longPress(e) {
  90. this.touch = true;
  91. this.startX = e.changedTouches[0].pageX
  92. this.startY = e.changedTouches[0].pageY
  93. let index = e.currentTarget.dataset.index;
  94. if(this.columns === 1) { // 单列时候X轴初始不做位移
  95. this.tranConstX = 0;
  96. } else { // 多列的时候计算X轴初始位移, 使 item 水平中心移动到点击处
  97. this.tranConstX = this.startX - this.item.width / 2 - this.itemWrap.left;
  98. }
  99. // 计算Y轴初始位移, 使 item 垂直中心移动到点击处
  100. this.teanConstY = this.startY - this.item.height / 2 - this.itemWrap.top;
  101. this.tranY = this.teanConstY;
  102. this.tranX = this.tranConstX;
  103. this.cur = index;
  104. this.curZ = index;
  105. // #ifndef H5
  106. uni.vibrateShort();
  107. // #endif
  108. },
  109. // 拖拽中
  110. touchMove(e) {
  111. if (!this.touch) return;
  112. let tranX = e.touches[0].pageX - this.startX + this.tranConstX; // TODO 原作者写的逻辑,但不知道为什么计算不对???
  113. let tranY = e.touches[0].pageY - this.startY + this.teanConstY;
  114. let overOnePage = this.overOnePage;
  115. // 判断是否超过一屏幕, 超过则需要判断当前位置动态滚动page的位置
  116. if(overOnePage) {
  117. if(e.touches[0].clientY > this.windowHeight - this.item.height) {
  118. uni.pageScrollTo({
  119. scrollTop: e.touches[0].pageY + this.item.height - this.windowHeight,
  120. duration: 300
  121. });
  122. } else if(e.touches[0].clientY < this.item.height) {
  123. uni.pageScrollTo({
  124. scrollTop: e.touches[0].pageY - this.item.height,
  125. duration: 300
  126. });
  127. }
  128. }
  129. this.tranX = tranX;
  130. this.tranY = tranY;
  131. let originKey = e.currentTarget.dataset.key;
  132. let endKey = this.calculateMoving(tranX, tranY);
  133. // 防止拖拽过程中发生乱序问题
  134. if (originKey == endKey || this.originKey == originKey) return;
  135. this.originKey = originKey;
  136. this.insert(originKey, endKey);
  137. },
  138. // 根据当前的手指偏移量计算目标key
  139. calculateMoving(tranX, tranY) {
  140. let rows = Math.ceil(this.list.length / this.columns) - 1;
  141. let i = Math.round(tranX / this.item.width);
  142. let j = Math.round(tranY / this.item.height);
  143. i = i > (this.columns - 1) ? (this.columns - 1) : i;
  144. i = i < 0 ? 0 : i;
  145. j = j < 0 ? 0 : j;
  146. j = j > rows ? rows : j;
  147. let endKey = i + this.columns * j;
  148. endKey = endKey >= this.list.length ? this.list.length - 1 : endKey;
  149. return endKey
  150. },
  151. // 根据起始key和目标key去重新计算每一项的新的key
  152. insert(origin, end) {
  153. let list;
  154. if (origin < end) {
  155. list = this.list.map((item) => {
  156. if (item.key > origin && item.key <= end) {
  157. item.key = item.key - 1;
  158. } else if (item.key == origin) {
  159. item.key = end;
  160. }
  161. return item
  162. });
  163. this.getPosition(list);
  164. } else if (origin > end) {
  165. list = this.list.map((item) => {
  166. if (item.key >= end && item.key < origin) {
  167. item.key = item.key + 1;
  168. } else if (item.key == origin) {
  169. item.key = end;
  170. }
  171. return item
  172. });
  173. this.getPosition(list);
  174. }
  175. },
  176. // 根据排序后 list 数据进行位移计算
  177. getPosition(data, vibrate = true) {
  178. let list = data.map((item, index) => {
  179. item.tranX = this.item.width * (item.key % this.columns);
  180. item.tranY = Math.floor(item.key / this.columns) * this.item.height;
  181. return item
  182. });
  183. this.list = list;
  184. if(!vibrate) return;
  185. this.itemTransition = true;
  186. // #ifndef H5
  187. uni.vibrateShort();
  188. // #endif
  189. let listData = [];
  190. list.forEach((item) => {
  191. listData[item.key] = item.data
  192. });
  193. // 元素位置发生改变了,触发change事件告诉父元素更新
  194. this.$emit('change', {listData: listData});
  195. },
  196. // 拖拽结束
  197. touchEnd() {
  198. if (!this.touch) return;
  199. this.clearData();
  200. },
  201. // 清除参数
  202. clearData() {
  203. this.originKey = -1;
  204. this.touch = false;
  205. this.cur = -1;
  206. this.tranX = 0;
  207. this.tranY = 0;
  208. // 延迟清空
  209. setTimeout(() => {
  210. this.curZ = -1;
  211. }, 300)
  212. },
  213. // 初始化加载
  214. init() {
  215. // 遍历数据源增加扩展项, 以用作排序使用
  216. let list = this.listData.map((item, index) => {
  217. return {
  218. key: index,
  219. tranX: 0,
  220. tranY: 0,
  221. data: item
  222. }
  223. });
  224. this.list = list;
  225. this.itemTransition = false;
  226. this.windowHeight = uni.getSystemInfoSync().windowHeight;
  227. setTimeout(() => {
  228. // 获取每一项的宽高等属性
  229. this.createSelectorQuery().select(".item").boundingClientRect((res) => {
  230. let rows = Math.ceil(this.list.length / this.columns);
  231. this.item = res;
  232. this.getPosition(this.list, false);
  233. let itemWrapHeight = rows * res.height;
  234. this.itemWrapHeight = itemWrapHeight;
  235. this.createSelectorQuery().select(".item-wrap").boundingClientRect((res) => {
  236. this.itemWrap = res;
  237. let overOnePage = itemWrapHeight + res.top > this.windowHeight;
  238. this.overOnePage = overOnePage;
  239. }).exec();
  240. }).exec();
  241. }, 300)
  242. }
  243. }
  244. }
  245. </script>
  246. <style lang="scss" scoped="scoped">
  247. .item-wrap {
  248. position: relative;
  249. .item {
  250. position: absolute;
  251. width: 100%;
  252. z-index: 1;
  253. &.itemTransition {
  254. transition: transform 0.3s;
  255. }
  256. &.zIndex {
  257. z-index: 2;
  258. }
  259. &.cur {
  260. background: #1998FE;
  261. transition: initial;
  262. }
  263. }
  264. }
  265. .info {
  266. position: relative;
  267. padding-top: 100%;
  268. background: #ffffff;
  269. margin-right: 5px;
  270. &:nth-child(3n){
  271. margin-right: 0px;
  272. }
  273. & > view {
  274. position: absolute;
  275. top: 0;
  276. left: 0;
  277. width: 100%;
  278. height: 100%;
  279. overflow: hidden;
  280. box-sizing: border-box;
  281. image {
  282. width: 100%;
  283. height: 100%;
  284. }
  285. }
  286. }
  287. .indicator {
  288. position: fixed;
  289. z-index: 99999;
  290. right: 0rpx;
  291. top: 50%;
  292. margin-top: -250rpx;
  293. padding: 20rpx;
  294. & > view {
  295. width: 36rpx;
  296. height: 500rpx;
  297. background: #ffffff;
  298. border-radius: 30rpx;
  299. box-shadow: 0 0 10rpx -4rpx rgba(0, 0, 0, 0.5);
  300. color: pink;
  301. padding-top: 90rpx;
  302. box-sizing: border-box;
  303. font-size: 24rpx;
  304. text-align: center;
  305. opacity: 0.8;
  306. }
  307. }
  308. </style>