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
325 lines
8.6 KiB
<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>
|