Skip to content

Commit

Permalink
feat(hooks): add useInView
Browse files Browse the repository at this point in the history
  • Loading branch information
nmsn committed May 15, 2023
1 parent 617e3c2 commit ade47d4
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/helper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { BasicTarget, TargetType, TargetValue } from '@/types';

export function getTargetElement<T extends TargetType>(target: BasicTarget<T>, defaultElement?: T) {
if (!isBrowser) {
return undefined;
}

if (!target) {
return defaultElement;
}

let targetElement: TargetValue<T>;

if (isFunction(target)) {
targetElement = target();
} else if ('current' in target) {
targetElement = target.current;
} else {
targetElement = target;
}

return targetElement;
}

const isBrowser = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);

export default isBrowser;

// eslint-disable-next-line @typescript-eslint/ban-types
export const isFunction = (value: unknown): value is Function => typeof value === 'function';
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { default as useClickAway } from './useClickAway';
export { default as useDeepEqualIdentifier } from './useDeepEqualIdentifier';
export { default as useDidUpdate } from './useDidUpdate';
export { default as useInView } from './useInView';
export { default as useLatest } from './useLatest';
export { default as useLoading } from './useLoading';
export { default as useMemoizedFn } from './useMemoizedFn';
Expand Down
40 changes: 40 additions & 0 deletions src/hooks/useInView/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* title: Default usage
* desc: Observe if the element is visible.
*
* title.zh-CN: 基础用法
* desc.zh-CN: 监听元素是否在可见区域内
*/

import React, { useRef } from 'react';

import { useInView } from '@nmsn/utils';

export default () => {
const ref = useRef(null);
const [inViewport] = useInView(ref);
return (
<div>
<div style={{ width: 300, height: 300, overflow: 'scroll', border: '1px solid' }}>
scroll here
<div style={{ height: 800 }}>
<div
ref={ref}
style={{
border: '1px solid',
height: 100,
width: 100,
textAlign: 'center',
marginTop: 80,
}}
>
observer dom
</div>
</div>
</div>
<div style={{ marginTop: 16, color: inViewport ? '#87d068' : '#f50' }}>
inViewport: {inViewport ? 'visible' : 'hidden'}
</div>
</div>
);
};
43 changes: 43 additions & 0 deletions src/hooks/useInView/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: useInView
toc: false
---

# useInView

观察元素是否在可见区域,以及元素可见比例。

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

## API

```typescript
const [inView, ratio] = useInView(
target,
options?: Options
);
```

### Params

| 参数 | 说明 | 类型 | 默认值 |
| ------- | ---------------- | ----------------------------------------------------------- | ------ |
| target | DOM 节点或者 ref | `Element` \| `() => Element` \| `MutableRefObject<Element>` | - |
| options | 设置 | `Options` | - |

### Options

更多信息参考 [Intersection Observer API](https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API)

| 参数 | 说明 | 类型 | 默认值 |
| ---------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------ |
| threshold | 可以是单一的 number 也可以是 number 数组,target 元素和 root 元素相交程度达到该值的时候 ratio 会被更新 | `number` \| `number[]` | - |
| rootMargin | 根(root)元素的外边距 | `string` | - |
| root | 指定根(root)元素,用于检查目标的可见性。必须是目标元素的父级元素,如果未指定或者为 null,则默认为浏览器视窗。 | `Element` \| `Document` \| `() => (Element/Document)` \| `MutableRefObject<Element>` | - |

### Result

| 参数 | 说明 | 类型 |
| ---------- | ----------------------------------------------------------- | ------------------------ |
| inView | 是否可见 | `boolean` \| `undefined` |
| ratio | 当前可见比例,在每次到达 `options.threshold` 设置节点时更新 | `number` \| `undefined` |
46 changes: 46 additions & 0 deletions src/hooks/useInView/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect, useState } from 'react';

import { getTargetElement } from '@/helper';
import { BasicTarget } from '@/types';

export interface Options {
rootMargin?: string;
threshold?: number | number[];
root?: BasicTarget<Element>;
}

function useInView(target: BasicTarget, options?: Options) {
const [state, setState] = useState<boolean>();
const [ratio, setRatio] = useState<number>();

useEffect(() => {
const el = getTargetElement(target);

if (!el) {
return;
}

const observer = new IntersectionObserver(
entries => {
for (const entry of entries) {
setRatio(entry.intersectionRatio);
setState(entry.isIntersecting);
}
},
{
...options,
root: getTargetElement(options?.root),
},
);

observer.observe(el);

return () => {
observer.disconnect();
};
}, [options?.rootMargin, options?.threshold]);

return [state, ratio] as const;
}

export default useInView;
11 changes: 11 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { MutableRefObject } from 'react';

export declare type ValueOf<T> = T[keyof T];

export declare type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;

export declare type GetKeyByValueType<T, Condition> = {
[K in keyof T]: T[K] extends Condition ? K : never;
}[keyof T];

export type TargetValue<T> = T | undefined | null;

export type TargetType = HTMLElement | Element | Window | Document;

export type BasicTarget<T extends TargetType = Element> =
| (() => TargetValue<T>)
| TargetValue<T>
| MutableRefObject<TargetValue<T>>;

1 comment on commit ade47d4

@vercel
Copy link

@vercel vercel bot commented on ade47d4 May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

utils – ./

nmsn-utils.vercel.app
utils-nmsn.vercel.app
utils-git-main-nmsn.vercel.app

Please sign in to comment.