Skip to content

Commit 6945734

Browse files
feat: Add useElementBounding and useResizeObserver (#166)
* feat: 新增 useElementBounding 与 useResizeObserver 方法 * test: 补充 useElementBounding 与 useResizeObserver 方法测试单元模块 * docs: add button and optimized doc * docs: fix title * docs: update note * type: guard --------- Co-authored-by: YongGit <[email protected]>
1 parent 93a1a33 commit 6945734

File tree

12 files changed

+487
-2
lines changed

12 files changed

+487
-2
lines changed

packages/hooks/docs/.vitepress/router.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ const Router = [
6464
{ text: 'useMedia', link: '/useMedia/' },
6565
{ text: 'useMouse', link: '/useMouse/' },
6666
{ text: 'useSize', link: '/useSize/' },
67+
{ text: 'useElementBounding', link: '/useElementBounding/' },
68+
{ text: 'useResizeObserver', link: '/useResizeObserver/' },
6769
{ text: 'useScroll', link: '/useScroll/' },
6870
{ text: 'useTitle', link: '/useTitle/' },
6971
{ text: 'useWinResize', link: '/useWinResize/' },

packages/hooks/src/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import useRequest, { clearUseRequestCache, useRequestProvider, useRequestDevToolsPlugin } from './useRequest'
1+
import useRequest, {
2+
clearUseRequestCache,
3+
useRequestProvider,
4+
useRequestDevToolsPlugin,
5+
} from './useRequest'
26
import useAsyncOrder from './useAsyncOrder'
37
import useBoolean from './useBoolean'
48
import useCookieState from './useCookieState'
@@ -46,6 +50,8 @@ import useVirtualList from './useVirtualList'
4650
import useWhyDidYouUpdate from './useWhyDidYouUpdate'
4751
import useWinResize from './useWinResize'
4852
import useWebSocket from './useWebSocket'
53+
import useElementBounding from './useElementBounding/index'
54+
import useResizeObserver from './useResizeObserver/index'
4955

5056
export {
5157
useRequest,
@@ -98,5 +104,7 @@ export {
98104
useVirtualList,
99105
useWhyDidYouUpdate,
100106
useWinResize,
101-
useWebSocket
107+
useWebSocket,
108+
useElementBounding,
109+
useResizeObserver,
102110
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import renderHook from 'test-utils/renderHook'
2+
import useElementBounding from '..'
3+
import { ref } from 'vue'
4+
import { UseElementBoundingReturnType } from '../index'
5+
6+
describe('useElementBounding', () => {
7+
it('callback element info', () => {
8+
const el = ref<HTMLElement>()
9+
const [callbackOptions] = renderHook(() => useElementBounding(el))
10+
11+
assertType<UseElementBoundingReturnType>(callbackOptions)
12+
})
13+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<template>
2+
<div>
3+
<textarea ref="valueRef" class="resizer" disabled />
4+
<p>width: {{ width }}</p>
5+
<p>height: {{ height }}</p>
6+
<p>left: {{ left }}</p>
7+
<p>right: {{ right }}</p>
8+
<p>top: {{ top }}</p>
9+
<p>bottom: {{ bottom }}</p>
10+
</div>
11+
</template>
12+
13+
<script lang="ts" setup>
14+
import { ref } from 'vue'
15+
import { useElementBounding } from 'vue-hooks-plus'
16+
17+
const valueRef = ref()
18+
const { width, height, left, top, right, bottom } = useElementBounding(valueRef)
19+
</script>
20+
21+
<style>
22+
.resize {
23+
min-width: 200px;
24+
min-height: 200px;
25+
max-width: 500px;
26+
max-height: 500px;
27+
}
28+
</style>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
map:
3+
# 映射到docs的路径
4+
path: /useElementBounding
5+
---
6+
7+
# useElementBounding
8+
9+
Dynamically obtain the size and coordinates of Dom elements.
10+
11+
## Code demonstration
12+
13+
<demo src="./demo/demo.vue"
14+
language="vue"
15+
title="Basic usage"
16+
desc="Use ref to set element that needs monitoring."> </demo>
17+
18+
## API
19+
20+
```typescript
21+
const isHovering = useHover(target, {
22+
reset?: boolean
23+
windowResize?: boolean
24+
windowScroll?: boolean
25+
immediate?: boolean
26+
})
27+
```
28+
29+
## Params
30+
31+
| Property | Description | Type | Default |
32+
| -------- | ------------------ | --------------------------------------------- | ------- |
33+
| target | DOM element or ref | `() => Element` \| `Element` \| `JSX.Element` | - |
34+
| options | More config | `UseElementBoundingOptions` | - |
35+
36+
## Options
37+
38+
| 参数 | 说明 | 类型 | 默认值 |
39+
| ------------ | --------------------------------------------------------- | --------- | ------ |
40+
| reset | When the component is mounted, initialize all values to 0 | `boolean` | true |
41+
| windowResize | Monitor window size changes | `boolean` | true |
42+
| windowScroll | Monitor window scrolling changes | `boolean` | true |
43+
| immediate | Executed immediately when the component is mounted | `boolean` | true |
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { onMounted, reactive, toRefs, Ref, watch } from 'vue'
2+
import useResizeObserver from '../useResizeObserver'
3+
import useEventListener from '../useEventListener'
4+
5+
import { BasicTarget, getTargetElement } from '../utils/domTarget'
6+
7+
export interface UseElementBoundingOptions {
8+
/**
9+
*
10+
* When the component is mounted, initialize all values to 0
11+
*
12+
* @default true
13+
*/
14+
reset?: boolean
15+
/**
16+
*
17+
* windowResize
18+
*
19+
* @default true
20+
*/
21+
windowResize?: boolean
22+
/**
23+
*
24+
* windowScroll
25+
*
26+
* @default true
27+
*/
28+
windowScroll?: boolean
29+
/**
30+
*
31+
* immediate
32+
*
33+
* @default true
34+
*/
35+
immediate?: boolean
36+
}
37+
38+
function keyisUseElementBoundingReturnTypeKey(key: string): key is keyof UseElementBoundingReturnType {
39+
return ['width', 'height', 'top', 'left', 'bottom', 'right'].includes(key)
40+
}
41+
42+
export interface UseElementBoundingReturnType {
43+
width: Ref<number>
44+
height: Ref<number>
45+
top: Ref<number>
46+
left: Ref<number>
47+
bottom: Ref<number>
48+
right: Ref<number>
49+
}
50+
51+
export default function useElementBounding(
52+
target: BasicTarget,
53+
options?: UseElementBoundingOptions,
54+
): UseElementBoundingReturnType {
55+
const { reset = true, windowResize = true, windowScroll = true, immediate = true } = options ?? {}
56+
const size = reactive({
57+
width: 0,
58+
height: 0,
59+
top: 0,
60+
left: 0,
61+
bottom: 0,
62+
right: 0,
63+
})
64+
65+
const update = () => {
66+
const targetDom = getTargetElement(target)
67+
68+
if (!targetDom) {
69+
if (reset) {
70+
Object.keys(size).forEach(key => {
71+
if (keyisUseElementBoundingReturnTypeKey(key))
72+
size[key] = 0
73+
})
74+
}
75+
76+
return
77+
}
78+
79+
if (targetDom) {
80+
const { width, height, top, left, bottom, right } = targetDom.getBoundingClientRect()
81+
82+
size.width = width
83+
size.height = height
84+
size.top = top
85+
size.left = left
86+
size.bottom = bottom
87+
size.right = right
88+
}
89+
}
90+
91+
if (windowResize) {
92+
useEventListener('resize', update, {
93+
passive: true,
94+
})
95+
}
96+
97+
if (windowScroll) {
98+
useEventListener('scroll', update, {
99+
capture: true,
100+
passive: true,
101+
})
102+
}
103+
104+
useResizeObserver(target, update)
105+
watch(() => getTargetElement(target), update)
106+
107+
onMounted(() => {
108+
immediate && update()
109+
})
110+
111+
return {
112+
...toRefs(size),
113+
}
114+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
map:
3+
# 映射到docs的路径
4+
path: /useElementBounding
5+
---
6+
7+
# useElementBounding
8+
9+
动态获取 Dom 元素的尺寸、坐标。
10+
11+
## 代码演示
12+
13+
<demo src="./demo/demo.vue"
14+
language="vue"
15+
title="基本用法"
16+
desc="使用 ref 设置需要监听的元素。"> </demo>
17+
18+
## API
19+
20+
```typescript
21+
const isHovering = useHover(target, {
22+
reset?: boolean
23+
windowResize?: boolean
24+
windowScroll?: boolean
25+
immediate?: boolean
26+
})
27+
```
28+
29+
## Params
30+
31+
| 参数 | 说明 | 类型 | 默认值 |
32+
| ------- | --------------------- | --------------------------------------------- | ------ |
33+
| target | DOM 节点或者 Ref 对象 | `() => Element` \| `Element` \| `JSX.Element` | - |
34+
| options | 额外的配置项 | `UseElementBoundingOptions` | - |
35+
36+
## Options
37+
38+
| 参数 | 说明 | 类型 | 默认值 |
39+
| ------------ | ---------------------------------- | --------- | ------ |
40+
| reset | 当组件为挂载时,将所有值初始化为 0 | `boolean` | true |
41+
| windowResize | 监听窗口尺寸变化 | `boolean` | true |
42+
| windowScroll | 监听窗口滚动变化 | `boolean` | true |
43+
| immediate | 组件挂载时立即执行 | `boolean` | true |
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import renderHook from 'test-utils/renderHook'
2+
import useResizeObserver from '..'
3+
import { ref } from 'vue'
4+
5+
describe('useResizeObserver', () => {
6+
it('stop resize observer', () => {
7+
const el = ref<HTMLElement>()
8+
const [callbackOptions] = renderHook(() => useResizeObserver(el, () => {}))
9+
const { stop } = callbackOptions
10+
11+
expect(stop).toBeDefined()
12+
})
13+
14+
it('is supported', () => {
15+
const el = ref<HTMLElement>()
16+
const [callbackOptions] = renderHook(() => useResizeObserver(el, () => {}))
17+
const { isSupported } = callbackOptions
18+
19+
assertType<boolean>(isSupported.value)
20+
})
21+
})
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<template>
2+
<div>
3+
<div>Click button resize the box to see changes</div>
4+
<br />
5+
<div>
6+
<vhp-button @click="handleCols"> col+1</vhp-button>
7+
<vhp-button style="margin-left: 8px;" @click="handleRows"> row+1 </vhp-button>
8+
</div>
9+
<br />
10+
<textarea
11+
ref="valueRef"
12+
class="resizer"
13+
:rows="rows"
14+
:cols="cols"
15+
disabled
16+
v-text="`width: ${width}\nheight: ${height}`"
17+
/>
18+
</div>
19+
</template>
20+
21+
<script lang="ts" setup>
22+
import { ref, toRefs, reactive } from 'vue'
23+
import { useResizeObserver } from 'vue-hooks-plus'
24+
25+
const cols = ref(40)
26+
const rows = ref(5)
27+
28+
const { width, height } = toRefs(
29+
reactive({
30+
width: 0,
31+
height: 0,
32+
}),
33+
)
34+
35+
const valueRef = ref()
36+
useResizeObserver(valueRef, entries => {
37+
const entry = entries[0]
38+
const { width: _w, height: _h } = entry.contentRect
39+
width.value = _w
40+
height.value = _h
41+
})
42+
43+
const handleCols = () => {
44+
cols.value += 1
45+
}
46+
const handleRows = () => {
47+
rows.value += 1
48+
}
49+
</script>
50+
51+
<style>
52+
.resize {
53+
min-width: 200px;
54+
min-height: 200px;
55+
max-width: 500px;
56+
max-height: 500px;
57+
}
58+
</style>

0 commit comments

Comments
 (0)