Taro版
使用 MovableArea
与MovableView
import React, { useEffect, useState } from "react";
import { MovableArea, MovableView, View } from "@tarojs/components";
export default () => {
const [changeId, setChangeId] = useState();
const [initData, setInitData] = useState([]);
const [dragList, setDragList] = useState([]);
const initMove = () => {
const setY = (index) => {
let y = 0;
let _data = dragList.slice(0, index);
_data.forEach((item) => {
y += item.height;
});
y /= 2;
return y;
};
setInitData(
dragList.map((item, index) => {
if (index) {
item.y = setY(index);
} else {
item.y = 0;
}
return item;
})
);
};
useEffect(() => {
setDragList([
{
id: 0,
height: 100,
color: "red",
y: 0,
},
{
id: 1,
height: 50,
color: "pink",
y: 0,
},
{
id: 2,
height: 200,
color: "blue",
y: 0,
},
{
id: 3,
height: 300,
color: "gray",
y: 0,
},
]);
}, []);
useEffect(() => {
let time = setTimeout(() => {
initMove();
}, 1000);
return () => {
clearTimeout(time);
};
}, [dragList]);
return (
<>
<MovableArea style="height: 600px; width:350px; background: #00bcd2;">
{initData.map((item) => {
return (
<MovableView
onChange={(e) => {
setChangeId(item.id);
setDragList(
dragList
.map((_item) => {
if (_item.id == item.id) {
_item.y = e.detail.y;
}
return _item;
})
.sort((a, b) => a.y - b.y)
);
}}
out-of-bounds
direction="vertical"
animation={false}
y={item.y}
style={{
width: "750rpx",
height: item.height + "rpx",
backgroundColor: item.color,
zIndex: item.id == changeId ? 2 : 1,
}}
>
<View>id: {item.id}</View>
</MovableView>
);
})}
</MovableArea>
</>
);
};
方法二(推荐)
import React, { useEffect, useState } from "react";
import Taro from "@tarojs/taro";
import {
View,
MovableArea,
MovableView,
Image,
Text,
} from "@tarojs/components";
import styles from "./index.module.less";
// Store
import { ListStore } from "../store";
import { useReachBottom } from "@tarojs/taro";
import { listDramas } from "@utils/request/opusmanage";
// 项高度
const itemHeight = 100;
// 假数据生成器
const getItems = (count) =>
Array.from({ length: count }, (v, index) => index).map((index) => ({
id: `${index}`,
top: `${index * itemHeight}`,
left: 0,
content: `内容${index}`,
}));
// 重新排序结果
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
export default function opusFeed3(props) {
const { listId } = props;
// 列表数据
const useListStore = ListStore.useContainer();
const { listStates, changeListStates } = useListStore;
// 作品列表
const { opusFeedList, pagination } = listStates;
// 下拉加载
const { current, pageSize, total } = pagination;
const [hasData, setHasData] = useState(true);
// 开启拖拽
const [isDrap, setIsDrap] = useState(false);
// 设置当前选中Index
const [changeIndex, setChangeIndex] = useState(-1);
const [state, setState] = useState([]);
const [listHeight, setListHeight] = useState(0);
// fetch
useEffect(() => {
// setState(getItems(4))
const opusFeedList2 = opusFeedList;
// 列表排序
opusFeedList2.forEach((item, index) => {
item.y = index * itemHeight;
});
setState([...opusFeedList2]);
}, [opusFeedList]);
// 计算列表总高度
useEffect(() => {
const length = state.length;
const height = length * 100;
setListHeight(height);
}, [state]);
// 长按:区分滑动和拖拽
const onLongPress = (e, i) => {
console.log("longPress");
// 设置当前项
setChangeIndex(i);
setIsDrap(true);
};
// 记录上次的位置
const onTouchStart = (e) => {
e.preventDefault();
};
// 添加限制:由于快速错拽出屏幕touchend后不触发,需要强制打断
const onTouchMove = (e) => {};
// 触摸结束
const onTouchEnd = (e, index) => {
console.log("onTouchEnd", "是否拖拽:", isDrap);
if (!isDrap) return;
const query = Taro.createSelectorQuery();
query.select(`.demoDOM_${index}`).boundingClientRect();
query.selectViewport().scrollOffset();
query.exec(function (res) {
const { top } = res[0];
const { scrollTop } = res[1];
console.log("当前索引", index);
// 动态索引
let updateIndex = Math.round(top / 100);
console.log("updateIndex", updateIndex);
// 移除索引(移除列表有几个元素)
let outListIndex = Math.round(scrollTop / 100);
console.log("listOutIndex", outListIndex);
// 计算:移动到目标索引
const moveIndex = updateIndex + outListIndex - 1;
console.log("moveIndex", moveIndex);
// 异步保底:强制异步执行
if (moveIndex === index) {
console.log("不替换");
const state2 = JSON.parse(JSON.stringify(state));
const [removed] = state2.splice(index, 1);
setState([...state2]);
setTimeout(() => {
const state3 = JSON.parse(JSON.stringify(state));
state3.splice(index, 0, removed);
setState([...state]);
}, 10);
} else {
console.log("替换");
// 节点替换
let state2 = reorder(state, index, moveIndex);
// 列表排序
state2.forEach((item, index) => {
item.y = index * itemHeight;
});
// 渲染总状态列表
changeListStates({ opusFeedList: [...state2] });
}
});
setIsDrap(false);
setChangeIndex(-1);
};
// 下拉刷新
const loadMoreData = async () => {
if (pageSize * current < total) {
console.log("加载更多");
try {
const obj = {
pagination: {
pageSize: 10,
current: current,
},
query: {
listId,
},
sort: {
type: "asc",
key: "update_time",
},
};
const data = await listDramas({ obj });
if (data?.list.length > 0) {
data.list.forEach((item, index) => {
item["choose"] = false;
item["id"] = index;
item["height"] = itemHeight;
item["y"] = index * itemHeight;
});
// 设置为下一页
pagination.current = pagination.current + 1;
const opusFeedList2 = opusFeedList.concat(data.list);
// 列表排序
opusFeedList2.forEach((item, index) => {
item.y = index * itemHeight;
});
changeListStates({
opusFeedList: opusFeedList2,
pagination: pagination,
});
}
} catch (error) {
console.log("error", error);
}
} else {
console.log("到底");
if (opusFeedList.length === total) {
setHasData(false);
} else {
const num = total - opusFeedList.length;
console.log("num", num);
try {
const obj = {
pagination: {
pageSize: num,
current: current,
},
query: {
listId,
},
sort: {
type: "asc",
key: "update_time",
},
};
const data = await listDramas({ obj });
if (data?.list.length > 0) {
data.list.forEach((item, index) => {
item["choose"] = false;
item["id"] = index;
item["height"] = itemHeight;
item["y"] = index * itemHeight;
});
// 设置为下一页
pagination.current = pagination.current + 1;
const opusFeedList2 = opusFeedList.concat(data.list);
// 列表排序
opusFeedList2.forEach((item, index) => {
item.y = index * itemHeight;
});
changeListStates({
opusFeedList: opusFeedList2,
pagination: pagination,
});
}
} catch (error) {
console.log("error", error);
}
setHasData(false);
}
}
};
// 监听滚动底部
useReachBottom(() => {
if (!hasData) {
return false;
}
loadMoreData();
});
const handleToggleItem = (index) => {
const opusFeedList2 = opusFeedList;
opusFeedList2.forEach((item, i) => {
if (index === i) {
item.choose = !item.choose;
}
});
changeListStates({ opusFeedList: [...opusFeedList2] });
};
return (
<View className={styles.pageBody}>
<MovableArea
style={{
height: listHeight,
width: "100%",
}}
className={`${styles.moveArea}`}
>
{state.map((item, index) => {
return (
<MovableView
style={{
width: "100%",
height: item.height,
zIndex: index === changeIndex ? 2 : 1,
backgroundColor: index === changeIndex ? "#fff" : "",
}}
className={`${styles.moveView} demoDOM_${index} `}
key={`${item.dramaId}_${index}`} // 必须唯一:随机值
direction="vertical"
x={0}
y={item.y}
onLongPress={(e) => onLongPress(e, index)}
onTouchStart={(e) => onTouchStart(e)}
onTouchMove={onTouchMove}
onTouchEnd={(e) => onTouchEnd(e, index)}
animation={false}
disabled={!isDrap}
>
<View
onClick={() => handleToggleItem(index)}
className={styles.item}
>
<View className={`${styles.opusFeed} `}>
<View
className={`${styles.opusOption} ${
item.choose ? styles.opusActive : ""
}`}
/>
<View
className={`${styles.opusFeedRight}`}
style={{
borderBottom:
index === changeIndex ? "" : "1px solid #E8E8E8",
}}
>
<View className={styles.opusImgWrap}>
<Image
className={styles.opusImg}
mode="aspectFill"
src={JSON.parse(item?.coverImage)[0]}
/>
</View>
<View className={styles.opusTitle}>
<Text className={styles.opusTitleText}>
{item?.title}
</Text>
</View>
<View className={`${styles.opusMenu} handler`} />
</View>
</View>
</View>
</MovableView>
);
})}
</MovableArea>
</View>
);
}