|
|
<template> <view> <view :style="{'overflow-x': overOnePage ? 'hidden' : 'initial'}"> <view class="item-wrap" :style="{'height': itemWrapHeight +'px'}"> <view class="item" :class="{'cur': cur == index, 'zIndex': curZ == index, 'itemTransition': itemTransition}" v-for="(item, index) in list" :key="index" :id="'item'+index" :data-key="item.key" :data-index="index" :style="[itemStyle(index, item, item.key)]" @longpress="longPress" @touchmove.stop="touchMove" @touchend.stop="touchEnd"> <view class="info"> <view> <image :src="item.data"></image> </view> </view> </view> </view> </view> <view v-if="overOnePage" class="indicator"> <view>滑动此区域滚动页面</view> </view> </view></template>
<script> export default { props: { }, data(){ return { touch: false, startX: 0, startY: 0, columns: 3, item: {}, tranX: 0, tranConstX: 0, itemWrap: {}, tranY: 0, teanConstY: 0, overOnePage: false, windowHeight: 0, originKey: null, list: [], cur: -1, curZ: -1, listData: [ 'https://picsum.photos/200', 'https://picsum.photos/200', 'https://picsum.photos/200', 'https://picsum.photos/200', 'https://picsum.photos/200/300'], itemWrapHeight: 0 } }, computed: { itemStyle(){ let that = this; return function(index, item, key){ let style_obj = { width: Math.floor(100 / that.columns) +'%', 'margin-top': Math.ceil((key+1) / that.columns) * 5 +'px' } let str = 'translate3d('; if(index === that.cur){ str += that.tranX +'px'; str += ', '; str += that.tranY +'px'; }else{ str += item.tranX +'px'; str += ', '; str += item.tranY +'px'; } str += ', 0px)'; style_obj.transform = str; return style_obj; } } }, mounted(){ this.init(); }, methods: { // 长按触发移动排序
longPress(e) { this.touch = true; this.startX = e.changedTouches[0].pageX this.startY = e.changedTouches[0].pageY let index = e.currentTarget.dataset.index; if(this.columns === 1) { // 单列时候X轴初始不做位移
this.tranConstX = 0; } else { // 多列的时候计算X轴初始位移, 使 item 水平中心移动到点击处
this.tranConstX = this.startX - this.item.width / 2 - this.itemWrap.left; } // 计算Y轴初始位移, 使 item 垂直中心移动到点击处
this.teanConstY = this.startY - this.item.height / 2 - this.itemWrap.top; this.tranY = this.teanConstY; this.tranX = this.tranConstX; this.cur = index; this.curZ = index; // #ifndef H5
uni.vibrateShort(); // #endif
}, // 拖拽中
touchMove(e) { if (!this.touch) return; let tranX = e.touches[0].pageX - this.startX + this.tranConstX; // TODO 原作者写的逻辑,但不知道为什么计算不对???
let tranY = e.touches[0].pageY - this.startY + this.teanConstY; let overOnePage = this.overOnePage; // 判断是否超过一屏幕, 超过则需要判断当前位置动态滚动page的位置
if(overOnePage) { if(e.touches[0].clientY > this.windowHeight - this.item.height) { uni.pageScrollTo({ scrollTop: e.touches[0].pageY + this.item.height - this.windowHeight, duration: 300 }); } else if(e.touches[0].clientY < this.item.height) { uni.pageScrollTo({ scrollTop: e.touches[0].pageY - this.item.height, duration: 300 }); } } this.tranX = tranX; this.tranY = tranY; let originKey = e.currentTarget.dataset.key; let endKey = this.calculateMoving(tranX, tranY); // 防止拖拽过程中发生乱序问题
if (originKey == endKey || this.originKey == originKey) return; this.originKey = originKey; this.insert(originKey, endKey); }, // 根据当前的手指偏移量计算目标key
calculateMoving(tranX, tranY) { let rows = Math.ceil(this.list.length / this.columns) - 1; let i = Math.round(tranX / this.item.width); let j = Math.round(tranY / this.item.height); i = i > (this.columns - 1) ? (this.columns - 1) : i; i = i < 0 ? 0 : i; j = j < 0 ? 0 : j; j = j > rows ? rows : j; let endKey = i + this.columns * j; endKey = endKey >= this.list.length ? this.list.length - 1 : endKey; return endKey }, // 根据起始key和目标key去重新计算每一项的新的key
insert(origin, end) { let list; if (origin < end) { list = this.list.map((item) => { if (item.key > origin && item.key <= end) { item.key = item.key - 1; } else if (item.key == origin) { item.key = end; } return item }); this.getPosition(list); } else if (origin > end) { list = this.list.map((item) => { if (item.key >= end && item.key < origin) { item.key = item.key + 1; } else if (item.key == origin) { item.key = end; } return item }); this.getPosition(list); } }, // 根据排序后 list 数据进行位移计算
getPosition(data, vibrate = true) { let list = data.map((item, index) => { item.tranX = this.item.width * (item.key % this.columns); item.tranY = Math.floor(item.key / this.columns) * this.item.height; return item }); this.list = list; if(!vibrate) return; this.itemTransition = true; // #ifndef H5
uni.vibrateShort(); // #endif
let listData = []; list.forEach((item) => { listData[item.key] = item.data }); // 元素位置发生改变了,触发change事件告诉父元素更新
this.$emit('change', {listData: listData}); }, // 拖拽结束
touchEnd() { if (!this.touch) return; this.clearData(); }, // 清除参数
clearData() { this.originKey = -1; this.touch = false; this.cur = -1; this.tranX = 0; this.tranY = 0; // 延迟清空
setTimeout(() => { this.curZ = -1; }, 300) }, // 初始化加载
init() { // 遍历数据源增加扩展项, 以用作排序使用
let list = this.listData.map((item, index) => { return { key: index, tranX: 0, tranY: 0, data: item } }); this.list = list; this.itemTransition = false; this.windowHeight = uni.getSystemInfoSync().windowHeight; setTimeout(() => { // 获取每一项的宽高等属性
this.createSelectorQuery().select(".item").boundingClientRect((res) => { let rows = Math.ceil(this.list.length / this.columns); this.item = res; this.getPosition(this.list, false); let itemWrapHeight = rows * res.height; this.itemWrapHeight = itemWrapHeight; this.createSelectorQuery().select(".item-wrap").boundingClientRect((res) => { this.itemWrap = res; let overOnePage = itemWrapHeight + res.top > this.windowHeight; this.overOnePage = overOnePage; }).exec(); }).exec(); }, 300) } } }</script>
<style lang="scss" scoped="scoped"> .item-wrap { position: relative; .item { position: absolute; width: 100%; z-index: 1; &.itemTransition { transition: transform 0.3s; } &.zIndex { z-index: 2; } &.cur { background: #1998FE; transition: initial; } } } .info { position: relative; padding-top: 100%; background: #ffffff; margin-right: 5px; &:nth-child(3n){ margin-right: 0px; } & > view { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; box-sizing: border-box; image { width: 100%; height: 100%; } } } .indicator { position: fixed; z-index: 99999; right: 0rpx; top: 50%; margin-top: -250rpx; padding: 20rpx; & > view { width: 36rpx; height: 500rpx; background: #ffffff; border-radius: 30rpx; box-shadow: 0 0 10rpx -4rpx rgba(0, 0, 0, 0.5); color: pink; padding-top: 90rpx; box-sizing: border-box; font-size: 24rpx; text-align: center; opacity: 0.8; } }</style>
|