@ohkit/draggable-box 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.modern.mjs +1 -1
- package/dist/index.modern.mjs.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/types/index.d.ts +7 -13
- package/dist/types/type.d.ts +5 -0
- package/dist/types/utils.d.ts +25 -4
- package/package.json +3 -3
- package/src/index.tsx +146 -118
- package/src/type.ts +5 -0
- package/src/utils.ts +64 -23
package/dist/types/utils.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 检查元素是否创建了新的定位上下文(包含块)
|
|
3
|
+
* @param element 要检查的元素
|
|
4
|
+
* @param includePosition 是否检查position属性(absolute/relative/fixed)
|
|
5
|
+
*/
|
|
6
|
+
export declare function isPositioningContextCreator(element: HTMLElement, includePosition: boolean): boolean;
|
|
1
7
|
/**
|
|
2
8
|
* 查找影响 fixed 定位的父元素
|
|
3
|
-
* 当父元素有 transform/filter/perspective 等属性时,fixed 定位会相对于该父元素
|
|
9
|
+
* 当父元素有 transform/filter/perspective/contain/will-change 等属性时,fixed 定位会相对于该父元素
|
|
4
10
|
*/
|
|
5
|
-
export declare function findFixedPositionParent(dom?: HTMLElement | null): HTMLElement;
|
|
11
|
+
export declare function findFixedPositionParent(dom?: HTMLElement | null): HTMLElement | null;
|
|
6
12
|
/**
|
|
7
13
|
* 查找 absolute 定位的父元素
|
|
8
|
-
*
|
|
14
|
+
* 查找最近的包含块创建者,包含所有可能影响absolute定位的属性
|
|
9
15
|
*/
|
|
10
|
-
export declare function findAbsolutePositionParent(dom?: HTMLElement | null): HTMLElement;
|
|
16
|
+
export declare function findAbsolutePositionParent(dom?: HTMLElement | null): HTMLElement | null;
|
|
11
17
|
/**
|
|
12
18
|
* 获取容器的缩放比例
|
|
13
19
|
* 通过比较元素的实际尺寸和 getBoundingClientRect 返回的尺寸来计算缩放比例
|
|
@@ -16,3 +22,18 @@ export declare function getScaleRatio(dom?: HTMLElement | null): {
|
|
|
16
22
|
scaleX: number;
|
|
17
23
|
scaleY: number;
|
|
18
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* 限制数值在指定范围内
|
|
27
|
+
*
|
|
28
|
+
* @param value 要限制的数值
|
|
29
|
+
* @param min 最小值
|
|
30
|
+
* @param max 最大值
|
|
31
|
+
* @returns 限制后的数值,确保在 [min, max] 范围内
|
|
32
|
+
*/
|
|
33
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* 检测当前环境是否支持触摸事件
|
|
36
|
+
*
|
|
37
|
+
* @returns 如果环境支持触摸事件返回 true,否则返回 false
|
|
38
|
+
*/
|
|
39
|
+
export declare function supportsTouchEvents(): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ohkit/draggable-box",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "可拖拽容器",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"draggable-box"
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"clean": "rm -rf dist"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@ohkit/dom-helper": "0.0.
|
|
30
|
+
"@ohkit/dom-helper": "0.0.7",
|
|
31
31
|
"@ohkit/prefix-classname": "^0.0.3"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": ">=17",
|
|
35
35
|
"react-dom": ">=17"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "e4e819440b17190101d14f35580fe68558fe86a9"
|
|
38
38
|
}
|
package/src/index.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
classNames as cx,
|
|
5
5
|
} from "@ohkit/prefix-classname";
|
|
6
6
|
import {addEventListener, addClass} from '@ohkit/dom-helper';
|
|
7
|
-
import {findFixedPositionParent, findAbsolutePositionParent, getScaleRatio} from './utils';
|
|
7
|
+
import {findFixedPositionParent, findAbsolutePositionParent, getScaleRatio, clamp, supportsTouchEvents} from './utils';
|
|
8
8
|
import {ValidPlacement} from './constants';
|
|
9
9
|
import {DraggableBoxProps, DraggableBoxState} from './type';
|
|
10
10
|
|
|
@@ -20,6 +20,7 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
20
20
|
disabled: false,
|
|
21
21
|
lockAxis: 'none',
|
|
22
22
|
showDragArea: false,
|
|
23
|
+
showDragAreaOverMoveDistanse: 5,
|
|
23
24
|
positionMode: 'fixed',
|
|
24
25
|
};
|
|
25
26
|
|
|
@@ -38,62 +39,65 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
getOtherXKey(xKey: 'left' | 'right') {
|
|
46
|
-
return xKey === 'left' ? 'right' : 'left';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// TODO:
|
|
50
|
-
updatePosition(yKey: 'top' | 'bottom', xKey: 'left' | 'right') {
|
|
51
|
-
const oYKey = this.getOtherYKey(yKey);
|
|
52
|
-
const oXKey = this.getOtherXKey(xKey);
|
|
53
|
-
this.setState({
|
|
54
|
-
[oYKey]: this.dragPositionRang.height - (this.state[yKey] || 0),
|
|
55
|
-
[oXKey]: this.dragPositionRang.width - (this.state[xKey] || 0),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
42
|
+
private prePositionMode: DraggableBoxProps['positionMode'];
|
|
43
|
+
private preDraggerRef: HTMLElement | null = null;
|
|
44
|
+
private container: HTMLElement | null = null;
|
|
58
45
|
/**
|
|
59
46
|
* 获取定位容器
|
|
60
47
|
* 根据 positionMode 返回对应的定位父元素
|
|
61
48
|
*/
|
|
62
|
-
private getContainer()
|
|
49
|
+
private getContainer(useCache = true) {
|
|
63
50
|
const { positionMode = 'fixed' } = this.props;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
if (!this.container || !useCache || this.prePositionMode !== positionMode || this.preDraggerRef !== this.draggerRef) {
|
|
52
|
+
this.prePositionMode = positionMode;
|
|
53
|
+
this.preDraggerRef = this.draggerRef;
|
|
54
|
+
this.container = positionMode === 'fixed'
|
|
55
|
+
? findFixedPositionParent(this.draggerRef)
|
|
56
|
+
: findAbsolutePositionParent(this.draggerRef);
|
|
57
|
+
}
|
|
58
|
+
return this.container;
|
|
67
59
|
}
|
|
68
60
|
|
|
69
61
|
/**
|
|
70
62
|
* 获取容器的尺寸和位置信息
|
|
71
63
|
*/
|
|
72
64
|
private getContainerRect() {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
const container = this.getContainer(false);
|
|
66
|
+
if (!container) {
|
|
67
|
+
return {
|
|
68
|
+
width: window.innerWidth,
|
|
69
|
+
height: window.innerHeight,
|
|
70
|
+
left: 0,
|
|
71
|
+
top: 0,
|
|
72
|
+
scrollLeft: 0,
|
|
73
|
+
scrollTop: 0,
|
|
74
|
+
scrollerScrollLeft: 0,
|
|
75
|
+
scrollerScrollTop: 0,
|
|
76
|
+
borderLeftWidth: 0,
|
|
77
|
+
borderTopWidth: 0
|
|
78
|
+
};
|
|
79
|
+
}
|
|
76
80
|
const containerRect = container.getBoundingClientRect();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
scrollerScrollTop: isFixed && isRoot ? 0 : rootScrollingElement.scrollTop
|
|
88
|
-
};
|
|
89
|
-
}
|
|
81
|
+
|
|
82
|
+
// 获取容器的border宽度(仅 top, left 对坐标计算有影响)
|
|
83
|
+
const containerStyle = window.getComputedStyle(container);
|
|
84
|
+
const borderLeftWidth = parseFloat(containerStyle.borderLeftWidth) || 0;
|
|
85
|
+
const borderTopWidth = parseFloat(containerStyle.borderTopWidth) || 0;
|
|
86
|
+
const borderRightWidth = parseFloat(containerStyle.borderRightWidth) || 0;
|
|
87
|
+
const borderBottomWidth = parseFloat(containerStyle.borderBottomWidth) || 0;
|
|
88
|
+
const yScrollerWidth = container.offsetWidth - container.clientWidth - borderLeftWidth - borderRightWidth;
|
|
89
|
+
const xScrollerHeight = container.offsetHeight - container.clientHeight - borderTopWidth - borderBottomWidth;
|
|
90
|
+
// console.log('yScrollerWidth, xScrollerHeight', yScrollerWidth, xScrollerHeight);
|
|
90
91
|
|
|
91
|
-
get windowSize() {
|
|
92
|
-
const container = this.getContainer();
|
|
93
|
-
const { clientWidth, clientHeight } = container;
|
|
94
92
|
return {
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
width: containerRect.width / this.cachedScaleX - borderLeftWidth - borderRightWidth - yScrollerWidth,
|
|
94
|
+
height: containerRect.height / this.cachedScaleY - borderTopWidth - borderBottomWidth - xScrollerHeight,
|
|
95
|
+
left: containerRect.left / this.cachedScaleX,
|
|
96
|
+
top: containerRect.top / this.cachedScaleY,
|
|
97
|
+
scrollLeft: container.scrollLeft,
|
|
98
|
+
scrollTop: container.scrollTop,
|
|
99
|
+
borderLeftWidth: borderLeftWidth,
|
|
100
|
+
borderTopWidth: borderTopWidth,
|
|
97
101
|
};
|
|
98
102
|
}
|
|
99
103
|
|
|
@@ -113,19 +117,21 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
113
117
|
get dragPositionBoundaries() {
|
|
114
118
|
const { boundsX, boundsY, placement = 'bottom-right' } = this.props;
|
|
115
119
|
const dragSize = this.dragBoxSize;
|
|
116
|
-
const
|
|
120
|
+
const {width: containerWidth, height: containerHeight} = this.getContainerRect();
|
|
117
121
|
const [placementY, placementX] = placement.split('-') as ['top' | 'bottom', 'left' | 'right'];
|
|
118
122
|
|
|
123
|
+
const defaultBounds = {
|
|
124
|
+
minX: 0,
|
|
125
|
+
maxX: Math.max(containerWidth - dragSize.width, 0),
|
|
126
|
+
minY: 0,
|
|
127
|
+
maxY: Math.max(containerHeight- dragSize.height, 0),
|
|
128
|
+
};
|
|
119
129
|
// 初始化边界
|
|
120
|
-
let minX =
|
|
121
|
-
let maxX = windowSize.width - dragSize.width;
|
|
122
|
-
let minY = 0;
|
|
123
|
-
let maxY = windowSize.height - dragSize.height;
|
|
130
|
+
let {minX, maxX, minY, maxY} = defaultBounds
|
|
124
131
|
|
|
125
132
|
// 处理X轴边界
|
|
126
133
|
if (boundsX) {
|
|
127
134
|
const [minBound, maxBound] = boundsX;
|
|
128
|
-
|
|
129
135
|
if (placementX === 'left') {
|
|
130
136
|
// 左边位置:boundsX=[左边最小距离, 左边最大距离]
|
|
131
137
|
if (minBound !== undefined) minX = Math.max(minX, minBound);
|
|
@@ -134,19 +140,14 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
134
140
|
// 右边位置:boundsX=[右边最小距离, 右边最大距离]
|
|
135
141
|
// 直接使用边界值作为right的限制
|
|
136
142
|
if (minBound !== undefined && maxBound !== undefined) {
|
|
137
|
-
minX = Math.max(minX,
|
|
138
|
-
maxX = Math.min(maxX,
|
|
143
|
+
minX = Math.max(minX, containerWidth - maxBound - dragSize.width);
|
|
144
|
+
maxX = Math.min(maxX, containerWidth - minBound - dragSize.width);
|
|
139
145
|
} else if (minBound !== undefined) {
|
|
140
146
|
// 只有minBound:设置最大边界,最小边界保持默认
|
|
141
|
-
maxX = Math.min(maxX,
|
|
147
|
+
maxX = Math.min(maxX, containerWidth - minBound - dragSize.width);
|
|
142
148
|
} else if (maxBound !== undefined) {
|
|
143
149
|
// 只有maxBound:设置最小边界,最大边界保持默认
|
|
144
|
-
minX = Math.max(minX,
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 确保最小边界不大于最大边界
|
|
148
|
-
if (minX > maxX) {
|
|
149
|
-
[minX, maxX] = [maxX, minX];
|
|
150
|
+
minX = Math.max(minX, containerWidth - maxBound - dragSize.width);
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
}
|
|
@@ -163,31 +164,25 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
163
164
|
// 底部位置:boundsY=[底边最小距离, 底边最大距离]
|
|
164
165
|
// 直接使用边界值作为bottom的限制
|
|
165
166
|
if (minBound !== undefined && maxBound !== undefined) {
|
|
166
|
-
minY = Math.max(minY,
|
|
167
|
-
maxY = Math.min(maxY,
|
|
167
|
+
minY = Math.max(minY, containerHeight - maxBound - dragSize.height);
|
|
168
|
+
maxY = Math.min(maxY, containerHeight - minBound - dragSize.height);
|
|
168
169
|
} else if (minBound !== undefined) {
|
|
169
170
|
// 只有minBound:设置最大边界,最小边界保持默认
|
|
170
|
-
maxY = Math.min(maxY,
|
|
171
|
+
maxY = Math.min(maxY, containerHeight - minBound - dragSize.height);
|
|
171
172
|
} else if (maxBound !== undefined) {
|
|
172
173
|
// 只有maxBound:设置最小边界,最大边界保持默认
|
|
173
|
-
minY = Math.max(minY,
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 确保最小边界不大于最大边界
|
|
177
|
-
if (minY > maxY) {
|
|
178
|
-
[minY, maxY] = [maxY, minY];
|
|
174
|
+
minY = Math.max(minY, containerHeight - maxBound - dragSize.height);
|
|
179
175
|
}
|
|
180
176
|
}
|
|
181
177
|
}
|
|
178
|
+
// 确保各个边界值在默认范围内
|
|
179
|
+
minX = clamp(minX, defaultBounds.minX, defaultBounds.maxX);
|
|
180
|
+
maxX = clamp(maxX, minX, defaultBounds.maxX);
|
|
181
|
+
minY = clamp(minY, defaultBounds.minY, defaultBounds.maxY);
|
|
182
|
+
maxY = clamp(maxY, minY, defaultBounds.maxY);
|
|
182
183
|
|
|
183
184
|
return { minX, maxX, minY, maxY };
|
|
184
185
|
}
|
|
185
|
-
|
|
186
|
-
// 保持向后兼容
|
|
187
|
-
get dragPositionRang() {
|
|
188
|
-
const { maxX, maxY } = this.dragPositionBoundaries;
|
|
189
|
-
return { width: maxX, height: maxY };
|
|
190
|
-
}
|
|
191
186
|
|
|
192
187
|
get curPositionKey() {
|
|
193
188
|
let {placement} = this.props;
|
|
@@ -232,35 +227,39 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
232
227
|
__bodyClassDisposer?: () => void;
|
|
233
228
|
__upDisposer?: () => void;
|
|
234
229
|
__resizeDisposer?: () => void;
|
|
230
|
+
__preventScrollDisposer?: () => void;
|
|
235
231
|
|
|
236
232
|
dragAreaRef: HTMLDivElement | null = null;
|
|
237
233
|
|
|
238
234
|
reportStartPosition() {
|
|
239
235
|
if (this.draggerRef) {
|
|
240
|
-
const { top, left } = this.draggerRef.getBoundingClientRect();
|
|
241
|
-
const containerRect = this.getContainerRect();
|
|
242
|
-
// console.log(containerRect, 'containerRect');
|
|
243
|
-
|
|
244
236
|
// 获取缩放比例
|
|
245
|
-
const { scaleX, scaleY } = getScaleRatio(this.
|
|
237
|
+
const { scaleX, scaleY } = getScaleRatio(this.getContainer());
|
|
246
238
|
this.cachedScaleX = scaleX;
|
|
247
239
|
this.cachedScaleY = scaleY;
|
|
240
|
+
const { top, left } = this.draggerRef.getBoundingClientRect();
|
|
241
|
+
const containerRect = this.getContainerRect();
|
|
242
|
+
// console.log(containerRect, 'containerRect');
|
|
248
243
|
|
|
249
244
|
// 计算相对于容器的位置,并除以缩放比例得到未缩放的坐标
|
|
250
|
-
this.startTop =
|
|
251
|
-
this.startLeft =
|
|
245
|
+
this.startTop = top / scaleY - containerRect.top + containerRect.scrollTop - containerRect.borderTopWidth;
|
|
246
|
+
this.startLeft = left / scaleY - containerRect.left + containerRect.scrollLeft - containerRect.borderLeftWidth;
|
|
252
247
|
}
|
|
253
248
|
}
|
|
254
249
|
|
|
255
|
-
enableDrag = () => {
|
|
250
|
+
enableDrag = (isTouch = false) => {
|
|
256
251
|
this.reportStartPosition();
|
|
257
252
|
this.__moveDisposer?.();
|
|
258
|
-
this.__moveDisposer = addEventListener(document, 'mousemove', (evt) => {
|
|
253
|
+
this.__moveDisposer = addEventListener(isTouch && this.draggerRef ? this.draggerRef : document, isTouch ? 'touchmove' : 'mousemove', (evt) => {
|
|
254
|
+
evt.stopPropagation();
|
|
255
|
+
if (isTouch) {
|
|
256
|
+
evt.preventDefault();
|
|
257
|
+
}
|
|
259
258
|
// INFO: 移动过程中禁止click事件
|
|
260
259
|
if (!this.__clickDisposer) {
|
|
261
260
|
const moveDistanse = Math.sqrt(Math.pow(this.dX, 2) + Math.pow(this.dY, 2));
|
|
262
|
-
// INFO: 移动超过
|
|
263
|
-
if (moveDistanse > 5) {
|
|
261
|
+
// INFO: 移动超过px?? 确保用户有真实的移动意愿,而不是手抖~~
|
|
262
|
+
if (moveDistanse > (this.props.showDragAreaOverMoveDistanse || 5)) {
|
|
264
263
|
this.__clickDisposer = addEventListener(
|
|
265
264
|
document,
|
|
266
265
|
'click',
|
|
@@ -277,17 +276,17 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
277
276
|
}
|
|
278
277
|
}
|
|
279
278
|
}
|
|
280
|
-
this.dragging(evt);
|
|
281
|
-
},
|
|
279
|
+
this.dragging(evt as TouchEvent | MouseEvent);
|
|
280
|
+
}, {
|
|
281
|
+
passive: !isTouch
|
|
282
|
+
});
|
|
282
283
|
|
|
283
284
|
this.__upDisposer?.();
|
|
284
285
|
this.__upDisposer = addEventListener(
|
|
285
286
|
document,
|
|
286
287
|
'mouseup',
|
|
287
|
-
(
|
|
288
|
+
() => {
|
|
288
289
|
this.endDrag();
|
|
289
|
-
evt.stopPropagation();
|
|
290
|
-
evt.preventDefault();
|
|
291
290
|
},
|
|
292
291
|
true
|
|
293
292
|
);
|
|
@@ -301,11 +300,11 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
301
300
|
this.axisX = evt.nativeEvent.pageX;
|
|
302
301
|
this.axisY = evt.nativeEvent.pageY;
|
|
303
302
|
if (!this.props.disabled) {
|
|
304
|
-
|
|
303
|
+
this.enableDrag();
|
|
305
304
|
}
|
|
306
305
|
};
|
|
307
306
|
|
|
308
|
-
dragging = (evt: MouseEvent) => {
|
|
307
|
+
dragging = (evt: MouseEvent | TouchEvent) => {
|
|
309
308
|
this.isDragging = true;
|
|
310
309
|
const { lockAxis } = this.props;
|
|
311
310
|
const { minX, maxX, minY, maxY } = this.dragPositionBoundaries;
|
|
@@ -314,9 +313,21 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
314
313
|
const scaleX = this.cachedScaleX;
|
|
315
314
|
const scaleY = this.cachedScaleY;
|
|
316
315
|
|
|
316
|
+
// 获取坐标
|
|
317
|
+
let pageX: number, pageY: number;
|
|
318
|
+
if (evt instanceof TouchEvent) {
|
|
319
|
+
const touch = evt.touches[0];
|
|
320
|
+
if (!touch) return;
|
|
321
|
+
pageX = touch.pageX;
|
|
322
|
+
pageY = touch.pageY;
|
|
323
|
+
} else {
|
|
324
|
+
pageX = evt.pageX;
|
|
325
|
+
pageY = evt.pageY;
|
|
326
|
+
}
|
|
327
|
+
|
|
317
328
|
// 计算原始偏移量(需要除以缩放比例)
|
|
318
|
-
this.dX = (
|
|
319
|
-
this.dY = (
|
|
329
|
+
this.dX = (pageX - (this.axisX || 0)) / scaleX;
|
|
330
|
+
this.dY = (pageY - (this.axisY || 0)) / scaleY;
|
|
320
331
|
|
|
321
332
|
// 应用方向锁定并计算变换值
|
|
322
333
|
let translateX = this.dX;
|
|
@@ -353,7 +364,16 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
353
364
|
if (this.draggerRef) {
|
|
354
365
|
this.draggerRef.style.transform = `translate(${translateX}px, ${translateY}px)`;
|
|
355
366
|
}
|
|
356
|
-
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
startTouchDrag = (evt: React.TouchEvent<HTMLDivElement>) => {
|
|
370
|
+
const touch = evt.touches[0];
|
|
371
|
+
if (!touch) return;
|
|
372
|
+
this.axisX = touch.pageX;
|
|
373
|
+
this.axisY = touch.pageY;
|
|
374
|
+
if (!this.props.disabled) {
|
|
375
|
+
this.enableDrag(true);
|
|
376
|
+
}
|
|
357
377
|
};
|
|
358
378
|
|
|
359
379
|
endDrag = () => {
|
|
@@ -369,8 +389,10 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
369
389
|
}
|
|
370
390
|
}
|
|
371
391
|
|
|
372
|
-
this.__moveDisposer
|
|
373
|
-
|
|
392
|
+
if (this.__moveDisposer) {
|
|
393
|
+
this.__moveDisposer();
|
|
394
|
+
this.__moveDisposer = undefined;
|
|
395
|
+
}
|
|
374
396
|
if (this.__clickDisposer) {
|
|
375
397
|
requestAnimationFrame(() => {
|
|
376
398
|
if (this.__clickDisposer) {
|
|
@@ -379,10 +401,14 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
379
401
|
}
|
|
380
402
|
});
|
|
381
403
|
}
|
|
382
|
-
this.__upDisposer
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
404
|
+
if (this.__upDisposer) {
|
|
405
|
+
this.__upDisposer();
|
|
406
|
+
this.__upDisposer = undefined;
|
|
407
|
+
}
|
|
408
|
+
if (this.__bodyClassDisposer) {
|
|
409
|
+
this.__bodyClassDisposer();
|
|
410
|
+
this.__bodyClassDisposer = undefined;
|
|
411
|
+
}
|
|
386
412
|
|
|
387
413
|
this.isDragging = false;
|
|
388
414
|
};
|
|
@@ -404,20 +430,12 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
404
430
|
this.dragAreaRef.style.height = '2px'; // 更细的虚线高度
|
|
405
431
|
this.dragAreaRef.style.left = `${minX}px`;
|
|
406
432
|
this.dragAreaRef.style.top = `${this.startTop + dragSize.height / 2}px`;
|
|
407
|
-
this.dragAreaRef.style.border = 'none';
|
|
408
|
-
this.dragAreaRef.style.backgroundColor = 'transparent'; // 透明背景
|
|
409
|
-
this.dragAreaRef.style.backgroundImage = 'linear-gradient(to right, var(--ohkit-color-primary, #1890ff) 50%, transparent 50%)';
|
|
410
|
-
this.dragAreaRef.style.backgroundSize = '4px 2px'; // 虚线模式
|
|
411
433
|
} else if (lockAxis === 'y') {
|
|
412
434
|
// 锁定X方向,显示为垂直虚线区域
|
|
413
435
|
this.dragAreaRef.style.width = '2px'; // 更细的虚线宽度
|
|
414
436
|
this.dragAreaRef.style.height = `${maxY - minY + dragSize.height}px`;
|
|
415
437
|
this.dragAreaRef.style.left = `${this.startLeft + dragSize.width / 2}px`;
|
|
416
438
|
this.dragAreaRef.style.top = `${minY}px`;
|
|
417
|
-
this.dragAreaRef.style.border = 'none';
|
|
418
|
-
this.dragAreaRef.style.backgroundColor = 'transparent'; // 透明背景
|
|
419
|
-
this.dragAreaRef.style.backgroundImage = 'linear-gradient(to bottom, var(--ohkit-color-primary, #1890ff) 50%, transparent 50%)';
|
|
420
|
-
this.dragAreaRef.style.backgroundSize = '2px 4px'; // 虚线模式
|
|
421
439
|
} else {
|
|
422
440
|
// 自由拖拽,显示完整区域
|
|
423
441
|
this.dragAreaRef.style.width = `${maxX - minX + dragSize.width}px`;
|
|
@@ -437,7 +455,7 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
437
455
|
calcPosition = () => {
|
|
438
456
|
const { lockAxis } = this.props;
|
|
439
457
|
const { minX, maxX, minY, maxY } = this.dragPositionBoundaries;
|
|
440
|
-
const
|
|
458
|
+
const {height, width} = this.getContainerRect();
|
|
441
459
|
|
|
442
460
|
// 计算新的位置
|
|
443
461
|
let newTop = this.startTop;
|
|
@@ -451,13 +469,13 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
451
469
|
}
|
|
452
470
|
|
|
453
471
|
// 应用边界限制
|
|
454
|
-
const realTop =
|
|
455
|
-
const realLeft =
|
|
456
|
-
const realBottom =
|
|
457
|
-
const realRight =
|
|
472
|
+
const realTop = clamp(newTop, minY, maxY);
|
|
473
|
+
const realLeft = clamp(newLeft, minX, maxX);
|
|
474
|
+
const realBottom = height - realTop - this.dragBoxSize.height;
|
|
475
|
+
const realRight = width - realLeft - this.dragBoxSize.width;
|
|
458
476
|
if (realTop !== this.state.top || realLeft !== this.state.left || this.state.bottom !== realBottom || this.state.right !== realRight) {
|
|
459
|
-
// console.log(minY, maxY, this.startTop, this.dY, newTop, realTop, 'calcPosition y');
|
|
460
|
-
// console.log(minX, maxX, this.startLeft, this.dX, newLeft, realLeft, 'calcPosition x');
|
|
477
|
+
// console.log(minY, maxY, this.startTop, this.dY, newTop, realTop, 'minY, maxY, this.startTop, this.dY, newTop, realTop --- calcPosition y');
|
|
478
|
+
// console.log(minX, maxX, this.startLeft, this.dX, newLeft, realLeft, 'minX, maxX, this.startLeft, this.dX, newLeft, realLeft ---calcPosition x');
|
|
461
479
|
this.setState({
|
|
462
480
|
top: realTop,
|
|
463
481
|
left: realLeft,
|
|
@@ -479,6 +497,13 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
479
497
|
this.__resizeDisposer = addEventListener(window, 'resize', () => {
|
|
480
498
|
this.calcPosition();
|
|
481
499
|
});
|
|
500
|
+
|
|
501
|
+
// 触屏设备时,阻止拖拽时滚动页面
|
|
502
|
+
if (supportsTouchEvents() && this.draggerRef) {
|
|
503
|
+
this.__preventScrollDisposer = addEventListener(this.draggerRef, 'touchmove', (evt) => {
|
|
504
|
+
evt.preventDefault();
|
|
505
|
+
});
|
|
506
|
+
}
|
|
482
507
|
}
|
|
483
508
|
|
|
484
509
|
componentWillUnmount() {
|
|
@@ -487,11 +512,12 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
487
512
|
this.__moveDisposer?.();
|
|
488
513
|
this.__clickDisposer?.();
|
|
489
514
|
this.__upDisposer?.();
|
|
515
|
+
this.__preventScrollDisposer?.();
|
|
490
516
|
}
|
|
491
517
|
|
|
492
518
|
render() {
|
|
493
519
|
const { className, zIndex, children, showDragArea, positionMode = 'fixed' } = this.props;
|
|
494
|
-
const { startDrag, endDrag } = this;
|
|
520
|
+
const { startDrag, startTouchDrag, endDrag } = this;
|
|
495
521
|
const stl = {
|
|
496
522
|
zIndex,
|
|
497
523
|
...this.position,
|
|
@@ -523,8 +549,10 @@ export class DraggableBox extends React.Component<DraggableBoxProps, DraggableBo
|
|
|
523
549
|
ref={(r) => {
|
|
524
550
|
this.draggerRef = r;
|
|
525
551
|
}}
|
|
526
|
-
|
|
527
|
-
|
|
552
|
+
onMouseDownCapture={startDrag}
|
|
553
|
+
onMouseUpCapture={endDrag}
|
|
554
|
+
onTouchStartCapture={startTouchDrag}
|
|
555
|
+
onTouchEndCapture={endDrag}
|
|
528
556
|
>
|
|
529
557
|
{children}
|
|
530
558
|
</div>
|
package/src/type.ts
CHANGED
|
@@ -51,6 +51,11 @@ export interface DraggableBoxProps {
|
|
|
51
51
|
* @default false
|
|
52
52
|
*/
|
|
53
53
|
showDragArea?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 拖拽过程中,超出多少px时才显示拖拽区域可视化
|
|
56
|
+
* @default 5
|
|
57
|
+
*/
|
|
58
|
+
showDragAreaOverMoveDistanse?: number;
|
|
54
59
|
/**
|
|
55
60
|
* 定位模式
|
|
56
61
|
* 'fixed' - 使用 fixed 定位(默认),动态查找影响 fixed 定位的父元素
|
package/src/utils.ts
CHANGED
|
@@ -1,42 +1,56 @@
|
|
|
1
1
|
import {findParent} from '@ohkit/dom-helper';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* 检查元素是否创建了新的定位上下文(包含块)
|
|
5
|
+
* @param element 要检查的元素
|
|
6
|
+
* @param includePosition 是否检查position属性(absolute/relative/fixed)
|
|
7
|
+
*/
|
|
8
|
+
export function isPositioningContextCreator(element: HTMLElement, includePosition: boolean): boolean {
|
|
9
|
+
const style = window.getComputedStyle(element);
|
|
10
|
+
const position = style.getPropertyValue('position');
|
|
11
|
+
const transform = style.getPropertyValue('transform');
|
|
12
|
+
const filter = style.getPropertyValue('filter');
|
|
13
|
+
const perspective = style.getPropertyValue('perspective');
|
|
14
|
+
const contain = style.getPropertyValue('contain');
|
|
15
|
+
const willChange = style.getPropertyValue('will-change');
|
|
16
|
+
|
|
17
|
+
return (includePosition && position !== 'static') ||
|
|
18
|
+
transform !== 'none' ||
|
|
19
|
+
filter !== 'none' ||
|
|
20
|
+
perspective !== 'none' ||
|
|
21
|
+
contain.includes('paint') ||
|
|
22
|
+
contain.includes('layout') ||
|
|
23
|
+
contain.includes('strict') ||
|
|
24
|
+
willChange.includes('transform') ||
|
|
25
|
+
willChange.includes('perspective') ||
|
|
26
|
+
willChange.includes('filter');
|
|
27
|
+
}
|
|
28
|
+
|
|
3
29
|
/**
|
|
4
30
|
* 查找影响 fixed 定位的父元素
|
|
5
|
-
* 当父元素有 transform/filter/perspective 等属性时,fixed 定位会相对于该父元素
|
|
31
|
+
* 当父元素有 transform/filter/perspective/contain/will-change 等属性时,fixed 定位会相对于该父元素
|
|
6
32
|
*/
|
|
7
|
-
export function findFixedPositionParent(dom?: HTMLElement | null)
|
|
33
|
+
export function findFixedPositionParent(dom?: HTMLElement | null) {
|
|
8
34
|
if (!dom) {
|
|
9
35
|
return document.documentElement;
|
|
10
36
|
}
|
|
11
37
|
const fixedPositionParent = findParent(dom, (parent) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const filter = style.getPropertyValue('filter');
|
|
15
|
-
const perspective = style.getPropertyValue('perspective');
|
|
16
|
-
|
|
17
|
-
// 检查是否有影响 fixed 定位的属性
|
|
18
|
-
if (transform !== 'none' || filter !== 'none' || perspective !== 'none') {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
}, {excludeOwn: true}) || document.documentElement; // 没有找到,返回 window(通过 document.documentElement)
|
|
38
|
+
return isPositioningContextCreator(parent, false);
|
|
39
|
+
}, {excludeOwn: true}); // 没有找到,返回 window(通过 document.documentElement)
|
|
22
40
|
return fixedPositionParent;
|
|
23
41
|
}
|
|
24
42
|
|
|
25
43
|
/**
|
|
26
44
|
* 查找 absolute 定位的父元素
|
|
27
|
-
*
|
|
45
|
+
* 查找最近的包含块创建者,包含所有可能影响absolute定位的属性
|
|
28
46
|
*/
|
|
29
|
-
export function findAbsolutePositionParent(dom?: HTMLElement | null)
|
|
47
|
+
export function findAbsolutePositionParent(dom?: HTMLElement | null) {
|
|
30
48
|
if (!dom) {
|
|
31
49
|
return document.body;
|
|
32
50
|
}
|
|
33
51
|
return findParent(dom, (parent) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (position !== 'static') {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
}, {excludeOwn: true}) || document.body;
|
|
52
|
+
return isPositioningContextCreator(parent, true);
|
|
53
|
+
}, {excludeOwn: true});
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
/**
|
|
@@ -49,9 +63,36 @@ export function getScaleRatio(dom?: HTMLElement | null): { scaleX: number; scale
|
|
|
49
63
|
}
|
|
50
64
|
// 通过比较 offsetWidth 和 getBoundingClientRect().width 来获取缩放比例
|
|
51
65
|
const rect = dom.getBoundingClientRect();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
let scaleX = 1;
|
|
67
|
+
let scaleY = 1;
|
|
68
|
+
// 扩大{magnification}倍进行计算, 相当于保留小数点后4位
|
|
69
|
+
const magnification = 10000;
|
|
70
|
+
scaleX = dom.offsetWidth > 0 ? Math.round(rect.width / dom.offsetWidth * magnification) / magnification : 1;
|
|
71
|
+
scaleY = dom.offsetHeight > 0 ? Math.round(rect.height / dom.offsetHeight * magnification) / magnification : 1;
|
|
72
|
+
// console.log('rect.width dom.offsetWidth', rect.width, dom.offsetWidth);
|
|
73
|
+
// console.log('rect.height dom.offsetHeight', rect.height, dom.offsetHeight);
|
|
55
74
|
// console.log('scaleX', scaleX, 'scaleY', scaleY);
|
|
56
75
|
return { scaleX, scaleY };
|
|
57
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 限制数值在指定范围内
|
|
80
|
+
*
|
|
81
|
+
* @param value 要限制的数值
|
|
82
|
+
* @param min 最小值
|
|
83
|
+
* @param max 最大值
|
|
84
|
+
* @returns 限制后的数值,确保在 [min, max] 范围内
|
|
85
|
+
*/
|
|
86
|
+
export function clamp(value: number, min: number, max: number) {
|
|
87
|
+
return Math.min(Math.max(value, min), max);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 检测当前环境是否支持触摸事件
|
|
93
|
+
*
|
|
94
|
+
* @returns 如果环境支持触摸事件返回 true,否则返回 false
|
|
95
|
+
*/
|
|
96
|
+
export function supportsTouchEvents() {
|
|
97
|
+
return typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0);
|
|
98
|
+
};
|