1
1
import { useEffect , useRef , useLayoutEffect , useCallback } from 'react' ;
2
2
import { findDOMNode } from 'react-dom' ;
3
3
4
+ type CanListenNode = Document | HTMLElement ;
5
+
4
6
export function useListener (
5
- nodeList : HTMLElement | HTMLElement [ ] ,
7
+ nodeList : CanListenNode | CanListenNode [ ] ,
6
8
eventName : string ,
7
9
callback : EventListenerOrEventListenerObject ,
8
10
useCapture : boolean ,
@@ -32,7 +34,7 @@ export function useListener(
32
34
}
33
35
34
36
/**
35
- * 将N个方法合并为一个链式调用的方法
37
+ * 将 N 个方法合并为一个链式调用的方法
36
38
* @return {Function } 合并后的方法
37
39
*
38
40
* @example
@@ -83,7 +85,7 @@ export function saveRef(ref: any) {
83
85
}
84
86
85
87
/**
86
- * 获取 position != static ,用来计算相对位置的容器
88
+ * 获取 position != static,用来计算相对位置的容器
87
89
* @param container
88
90
* @returns
89
91
*/
@@ -117,23 +119,22 @@ export const getOverflowNodes = (targetNode: HTMLElement, container: HTMLElement
117
119
}
118
120
119
121
const overflowNodes : HTMLElement [ ] = [ ] ;
120
- // 使用getViewPort方式获取滚动节点 ,考虑元素可能会跳出最近的滚动容器的情况(绝对定位,containingBlock等原因 )
122
+ // 使用 getViewPort 方式获取滚动节点 ,考虑元素可能会跳出最近的滚动容器的情况(绝对定位,containingBlock 等原因 )
121
123
// 原先的只获取了可滚动的滚动容器(滚动高度超出容器高度),改成只要具有滚动属性即可,因为后面可能会发生内容变化导致其变得可滚动了
122
- let overflowNode = getViewPort ( targetNode . parentElement ) ;
124
+ let overflowNode = getViewPortExcludeSelf ( targetNode ) ;
123
125
124
126
while ( overflowNode && container . contains ( overflowNode ) && container !== overflowNode ) {
125
127
overflowNodes . push ( overflowNode ) ;
126
- if ( overflowNode . parentElement ) {
127
- overflowNode = getViewPort ( overflowNode . parentElement ) ;
128
- } else {
129
- break ;
130
- }
128
+ overflowNode = getViewPortExcludeSelf ( overflowNode ) ;
129
+ }
130
+ if ( isScrollableElement ( container ) ) {
131
+ overflowNodes . push ( container ) ;
131
132
}
132
133
return overflowNodes ;
133
134
} ;
134
135
135
136
/**
136
- * 是否是webkit内核
137
+ * 是否是 webkit 内核
137
138
*/
138
139
function isWebKit ( ) : boolean {
139
140
if ( typeof CSS === 'undefined' || ! CSS . supports ) {
@@ -194,10 +195,38 @@ function getContainingBlock(element: HTMLElement): HTMLElement | null {
194
195
*/
195
196
const isContentClippedElement = ( element : Element ) => {
196
197
const overflow = getStyle ( element , 'overflow' ) ;
197
- // 测试环境overflow默认为 ''
198
- return overflow && overflow !== 'visible' ;
198
+ // 测试环境 overflow 默认为 ''
199
+ return ( overflow && overflow !== 'visible' ) || element === document . documentElement ;
199
200
} ;
200
201
202
+ /**
203
+ * 判断元素是否是可滚动的元素,且滚动内容尺寸大于元素尺寸
204
+ */
205
+ function isScrollableElement ( element : Element ) {
206
+ const overflow = getStyle ( element , 'overflow' ) ;
207
+ // 这里兼容老的逻辑判断,忽略 hidden
208
+ if ( element === document . documentElement || ( overflow && overflow . match ( / a u t o | s c r o l l / ) ) ) {
209
+ const { clientWidth, clientHeight, scrollWidth, scrollHeight } = element ;
210
+ // 仅当实际滚动高度大于元素尺寸时,才被视作是可滚动元素
211
+ return clientHeight !== scrollHeight || clientWidth !== scrollWidth ;
212
+ }
213
+ return false ;
214
+ }
215
+
216
+ export function getRect ( target : HTMLElement ) {
217
+ if ( target === document . documentElement ) {
218
+ const { clientWidth : width , clientHeight : height } = target ;
219
+ return {
220
+ left : 0 ,
221
+ top : 0 ,
222
+ width,
223
+ height,
224
+ } ;
225
+ }
226
+ const { left, top, width, height } = target . getBoundingClientRect ( ) ;
227
+ return { left, top, width, height } ;
228
+ }
229
+
201
230
/**
202
231
* 获取最近的裁剪内容区域的祖先节点
203
232
*/
@@ -229,6 +258,24 @@ function getOffsetParent(element: HTMLElement): HTMLElement | null {
229
258
return offsetParent as HTMLElement ;
230
259
}
231
260
261
+ export function getViewPortExcludeSelf ( target : HTMLElement ) {
262
+ const fallbackViewportElement = document . documentElement ;
263
+ if ( ! target ) {
264
+ return fallbackViewportElement ;
265
+ }
266
+ const parent = [ 'fixed' , 'absolute' ] . includes ( getStyle ( target , 'position' ) )
267
+ ? getOffsetParent ( target ) || getContainingBlock ( target )
268
+ : target . parentElement ;
269
+
270
+ if ( ! parent ) {
271
+ return fallbackViewportElement ;
272
+ }
273
+ if ( isContentClippedElement ( parent ) ) {
274
+ return parent ;
275
+ }
276
+ return getViewPortExcludeSelf ( parent ) ;
277
+ }
278
+
232
279
/**
233
280
* 获取可视区域,用来计算弹窗应该相对哪个节点做上下左右的位置变化。
234
281
* @param container
@@ -241,25 +288,28 @@ export function getViewPort(container: HTMLElement): HTMLElement {
241
288
return fallbackViewportElement ;
242
289
}
243
290
244
- // 若 container 本身就是滚动容器,则直接返回
245
- if ( isContentClippedElement ( container ) ) {
246
- return container ;
247
- }
248
-
249
- // 若 container 的 position 是 absolute 或 fixed,则有可能会脱离其最近的滚动容器,需要根据 offsetParent 和 containing block来综合判断
291
+ // 若 container 的 position 是 absolute 或 fixed,则有可能会脱离其最近的滚动容器,需要根据 offsetParent 和 containing block 来综合判断
250
292
if ( [ 'fixed' , 'absolute' ] . includes ( getStyle ( container , 'position' ) ) ) {
293
+ if ( isContentClippedElement ( container ) ) {
294
+ return container ;
295
+ }
296
+
251
297
// 先获取定位节点(若无则使用 containerBlock)
252
298
const offsetParent = getOffsetParent ( container ) || getContainingBlock ( container ) ;
253
299
// 拥有定位节点
254
300
if ( offsetParent ) {
255
301
// 从定位节点开始寻找父级滚动容器
256
302
return getViewPort ( offsetParent ) ;
257
303
} else {
258
- // 无定位节点,也无containingBlock影响 ,则用 fallback元素
304
+ // 无定位节点,也无 containingBlock 影响 ,则用 fallback 元素
259
305
return fallbackViewportElement ;
260
306
}
261
307
}
262
308
309
+ if ( isContentClippedElement ( container ) ) {
310
+ return container ;
311
+ }
312
+
263
313
if ( container . parentElement ) {
264
314
return getViewPort ( container . parentElement ) || fallbackViewportElement ;
265
315
}
@@ -335,7 +385,7 @@ export function debounce(func: Function, wait: number) {
335
385
*/
336
386
export function getViewTopLeft ( node : HTMLElement ) {
337
387
/**
338
- * document.body 向下滚动后 scrollTop 一直为0 ,同时 top=-xx 为负数,相当于本身是没有滚动条的,这个逻辑是正确的。
388
+ * document.body 向下滚动后 scrollTop 一直为 0 ,同时 top=-xx 为负数,相当于本身是没有滚动条的,这个逻辑是正确的。
339
389
* document.documentElement 向下滚动后 scrollTop/top 都在变化,会影响计算逻辑,所以这里写死 0
340
390
*/
341
391
if ( node === document . documentElement ) {
0 commit comments