react 可拖拽静态组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { useRef } from "react";
import clamp from "lodash-es/clamp";
import swap from "lodash-move";
import { useSprings, animated, config } from "@react-spring/web";
import { useDrag } from "@use-gesture/react";
import { MAX_COL, SX, SY } from "@/constants/combFile";
import "./index.css";

const OperatePic = ({ items }) => {
const height = (Math.floor(items.length / MAX_COL) + 1) * SY;
const width =
(Math.floor(items.length / MAX_COL) >= 1 ? MAX_COL : items.length) * SX;

// 创建两个springs,一个用于拖动时的动画参数,一个用于没有拖动时平移的参数
const fn =
(
order,
active = false,
originalIndex = 0,
curIndex = 0,
x = 0,
y = 0
) =>
(index) =>
active && index === originalIndex
? {
x: (curIndex % MAX_COL) * SX + x,
y: Math.floor(curIndex / MAX_COL) * SY + y,
scale: 1.1,
zIndex: 1,
shadow: 15,
immediate: (key) => key === "zIndex",
config: (key) =>
key === "y" ? config.stiff : config.default,
}
: {
x: (order.indexOf(index) % MAX_COL) * SX,
y: Math.floor(order.indexOf(index) / MAX_COL) * SY,
scale: 1,
zIndex: 0,
shadow: 1,
immediate: false,
};

const order = useRef(items.map((_, index) => index)); // 将索引存储为本地引用,这表示项目顺序,将在拖动时更新
const [springs, api] = useSprings(items.length, fn(order.current)); // 创建spring,每个spring对应一个项目,控制其变形、缩放等。
const bind = useDrag(
({ args: [originalIndex], active, movement: [x, y] }) => {
const curIndex = order.current.indexOf(originalIndex); // 获取当前拖动的元素所在的索引位置
const curRow = clamp(
// 获取当前拖动的元素所在的行数
Math.round((Math.floor(curIndex / MAX_COL) * SY + y) / SY),
0,
Math.floor(items.length / MAX_COL)
);
const curCol = clamp(
// 获取当前拖动的元素所在的列数
Math.round(((curIndex % MAX_COL) * SX + x) / SX),
0,
Math.max(items.length - 1, MAX_COL)
);
console.log(curRow, curCol, curIndex, originalIndex, x, y);
const newOrder = swap(
order.current,
curIndex,
curRow * MAX_COL + curCol
); // 交换当前拖动的元素和目标位置的元素的索引,得到新的索引顺序
api.start(fn(newOrder, active, originalIndex, curIndex, x, y)); // 可以在不依靠render的情况下,进行动画的更新
if (!active) order.current = newOrder; // 如果拖动结束,更新索引顺序
}
);

return (
<div
className="content"
style={{
height: height,
width: width,
}}
>
{springs.map(({ zIndex, shadow, x, y, scale }, i) => (
<animated.div
{...bind(i)} // 绑定拖动事件
key={i} // 绑定key
style={{
width: SX - 20,
height: SY - 20,
// 与fn的返回值对应
zIndex,
boxShadow: shadow.to(
(s) =>
`rgba(0, 0, 0, 0.15) 0px ${s}px ${2 * s}px 0px`
),
x,
y,
scale,
}}
children={items[i]} // 显示内容
/>
))}
</div>
);
};

export default OperatePic;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.content {
width: 320px;
}

.content > div {
position: absolute;
border-radius: 10px;
font-size: 14.5px;
background: hsla(0, 0%, 90%, 0.8);
touch-action: none;
cursor: grab;
border-right: 0;
border-bottom: 0;
box-shadow: 5px 5px 5px -4px rgba(180, 160, 120, 0.6);
}

.content > div:hover {
cursor: grabbing;
}