Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export const menus = [
'useScroll',
'useSize',
'useFocusWithin',
'useStickyFixed',
],
},
{
Expand Down
2 changes: 2 additions & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import useWebSocket from './useWebSocket';
import useWhyDidYouUpdate from './useWhyDidYouUpdate';
import useMutationObserver from './useMutationObserver';
import useTheme from './useTheme';
import useStickyFixed from './useStickyFixed';

export {
useRequest,
Expand Down Expand Up @@ -158,4 +159,5 @@ export {
useResetState,
useMutationObserver,
useTheme,
useStickyFixed,
};
42 changes: 42 additions & 0 deletions packages/hooks/src/useStickyFixed/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { act, renderHook } from '@testing-library/react';
import useStickyFixed from '../index';

const scrollElement = document.createElement('div');
scrollElement.style.overflowY = 'scroll';
scrollElement.style.height = '200px'; // 设置高度以允许滚动
scrollElement.style.width = '200px'; // 设置高度以允许滚动
document.body.appendChild(scrollElement);

const topElement = document.createElement('div'); //top元素用于填充
topElement.style.height = '100px';
scrollElement.appendChild(topElement);

const targetElement = document.createElement('div'); // 模拟 sticky 的元素
targetElement.style.position = 'sticky';
targetElement.style.top = '0';
targetElement.style.height = '20px'; //其他元素用于填充
scrollElement.appendChild(targetElement);

const bottomElement = document.createElement('div'); //bottom元素用于填充
bottomElement.style.height = '200px';
scrollElement.appendChild(bottomElement);

describe('useStickyFixed', () => {
it('should set state to false when not scrolling', () => {
const { result } = renderHook(() => useStickyFixed(targetElement, { scrollTarget: scrollElement }));

// 开始未滚动 返回false
expect(result.current[0]).toBe(false);
});

it('should not throw if target is not found', () => {
const { result } = renderHook(() => useStickyFixed(null, { scrollTarget: scrollElement }));

act(() => {
scrollElement.scrollTop = 0;
scrollElement.dispatchEvent(new Event('scroll'));
});

expect(result.current[0]).toBe(false); // 没有抛出错误
});
});
47 changes: 47 additions & 0 deletions packages/hooks/src/useStickyFixed/demo/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* title: Basic usage
* desc: Need to input the sticky positioning element target and the rolling container scrollTarget, ScrollTarget defaults to document
*
* title.zh-CN: 基础用法
* desc.zh-CN: 需要传入粘性定位元素target和滚动容器scrollTarget, scrollTarget默认为document
*
*/

import React, { useRef, useState } from 'react';
import { useStickyFixed } from 'ahooks';

export default () => {
const [topV, setTopV] = useState(0);

const targetRef = useRef(null);
const scrollTargetRef = useRef(null);

const [isFixed] = useStickyFixed(targetRef, { scrollTarget: scrollTargetRef });

const fixedStyle = { background: 'pink' };

return (
<>
<div style={{ marginBottom: 16 }}>
top: <input type="number" step={5} value={topV} onChange={(e) => setTopV(Number(e.target.value))} />
</div>

<div ref={scrollTargetRef} style={{ height: 200, width: 500, border: '1px solid #000', overflowY: 'scroll' }}>
<div style={{ height: 100 }}>top content</div>
<div
ref={targetRef}
style={{
position: 'sticky',
border: '2px dashed pink',
top: topV,
...(isFixed && fixedStyle),
}}>
sticky dom
</div>
<div style={{ height: 200, marginTop: 50 }}>bottom content</div>
</div>

<div style={{ marginTop: 16 }}> isFixed :{`${isFixed}`}</div>
</>
);
};
46 changes: 46 additions & 0 deletions packages/hooks/src/useStickyFixed/demo/demo2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* title: Pass in DOM element
* desc: Pass in a function that returns the DOM element.
*
* title.zh-CN: 传入 DOM 元素
* desc.zh-CN: 传入 function 并返回一个 dom 元素。
*
*/

import React, { useState } from 'react';
import { useStickyFixed } from 'ahooks';

export default () => {
const [topV, setTopV] = useState(0);

const [isFixed] = useStickyFixed(() => document.getElementById('target'), {
scrollTarget: () => document.getElementById('scrollTarget'),
});

const fixedStyle = { background: 'pink' };

return (
<>
<div style={{ marginBottom: 16 }}>
top: <input type="number" step={5} value={topV} onChange={(e) => setTopV(Number(e.target.value))} />
</div>

<div id="scrollTarget" style={{ height: 200, width: 500, border: '1px solid #000', overflowY: 'scroll' }}>
<div style={{ height: 100 }}>top content</div>
<div
id="target"
style={{
position: 'sticky',
border: '2px dashed pink',
top: topV,
...(isFixed && fixedStyle),
}}>
sticky dom
</div>
<div style={{ height: 200, marginTop: 50 }}>bottom content</div>
</div>

<div style={{ marginTop: 16 }}> isFixed :{`${isFixed}`}</div>
</>
);
};
47 changes: 47 additions & 0 deletions packages/hooks/src/useStickyFixed/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
nav:
path: /hooks
---

# useStickyFixed

Observe whether the element of 'position: sticky' is in a fixed suction state

## Examples

### Basic usage

<code src="./demo/demo1.tsx" />

### Pass in DOM element

<code src="./demo/demo2.tsx" />

## API

```typescript
const [isFixed] = useStickyFixed(targetRef, { scrollTarget });
```

### Params

| Property | Description | Type | Default |
| -------- | ---------------------------------------- | ----------------------------------------------------------- | ------- |
| target | `position: sticky` 's Dom element or ref | `() => Element` \| `Element` \| `MutableRefObject<Element>` | - |
| options | More config | `Options` | - |

### Options

| Property | Description | Type | Default |
| ------------- | --------------------------------------------------- | ----------------------------------------------------------- | ------- |
| scrollTarget | The element or ref of the DOM in the scrolling area | `() => Element` \| `Element` \| `MutableRefObject<Element>` | document|


### Result

| Property | Description | Type |
| ------------- | ------------------------------------------------------ | --------- |
| isFixed | Is the `position: sticky` 's Dom in the 'fixed' state | `boolean` |



48 changes: 48 additions & 0 deletions packages/hooks/src/useStickyFixed/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useRef } from 'react';
import { getTargetElement, type BasicTarget } from '../utils/domTarget';
import useEffectWithTarget from '../utils/useEffectWithTarget';
import useRafState from '../useRafState';

function useStickyFixed(
target: BasicTarget<Element>,
options?: {
scrollTarget?: BasicTarget<Element | Document>;
},
): [boolean] {
const { scrollTarget } = options || {};

const [state, setState] = useRafState<boolean>(false);
const lastTopRef = useRef(0);

useEffectWithTarget(
() => {
const scrollElement = getTargetElement(scrollTarget, document);
if (!scrollElement) {
return;
}

const stickyElement = getTargetElement(target);
if (!stickyElement) {
return;
}

const handleScroll = () => {
const rect = stickyElement.getBoundingClientRect();
const currentTop = rect.top;
const lastTop = lastTopRef.current;
setState(currentTop === lastTop);
lastTopRef.current = currentTop;
};

scrollElement.addEventListener('scroll', handleScroll);
return () => {
scrollElement.removeEventListener('scroll', handleScroll);
};
},
[],
target,
);

return [state];
}
export default useStickyFixed;
43 changes: 43 additions & 0 deletions packages/hooks/src/useStickyFixed/index.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
nav:
path: /hooks
---

# useStickyFixed

观察粘性定位(`position: sticky`)的元素,是否处于吸顶固定状态

## 代码演示

### 基础用法

<code src="./demo/demo1.tsx" />

### 传入 DOM 元素

<code src="./demo/demo2.tsx" />

## API

```typescript
const [isFixed] = useStickyFixed(targetRef, { scrollTarget });
```

### Params

| 参数 | 说明 | 类型 | 默认值 |
| ------- | -------------------------------- | ---------------------------------------------------------- | ------ |
| target | 粘性定位的 DOM 节点或者 Ref 对象 | `Element` \|`() => Element` \| `MutableRefObject<Element>` | - |
| options | 额外的配置项 | `Options` | - |

### Options

| 参数 | 说明 | 类型 | 默认值 |
| ------------ | -------------------------------- | ------------------------------------------------------------------------ | -------- |
| scrollTarget | 滚动区域的 DOM 节点或者 Ref 对象 | `Element` \| `Document` \|`() => Element` \| `MutableRefObject<Element>` | document |

### Result

| 参数 | 说明 | 类型 |
| ------- | --------------------------------- | --------- |
| isFixed | 粘性定位元素是否处于 `fixed` 状态 | `boolean` |