详情小程序
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.

346 lines
9.1 KiB

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