Skip to content

Commit

Permalink
feat: add useStickyFixed hook
Browse files Browse the repository at this point in the history
  • Loading branch information
admin-zlj committed Feb 8, 2025
1 parent c7bb04c commit 8274643
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 0 deletions.
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,
};
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` |

0 comments on commit 8274643

Please sign in to comment.