New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@fly4react/observer

Package Overview
Dependencies
Maintainers
1
Versions
56
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fly4react/observer - npm Package Compare versions

Comparing version
1.24.0
to
1.25.0
+52
dist/hooks/useLazyElementPositionEffect.d.ts
import type React from 'react';
import type { UseLazyElementPositionEffectOptions } from '../types';
/**
* 延迟计算元素位置并定时检测 Hook
*
* 基于 useLazyElementPositionRef,增加定时检测功能。
* 返回一个函数,调用时才开始执行定时检测。
* 每隔指定时间间隔检测一次元素位置,如果位置发生变化则调用回调函数。
*
* 浏览器兼容性:
* - 支持 IntersectionObserver 的浏览器:使用原生 API,性能最佳
* - 不支持 IntersectionObserver 的浏览器:使用标准的 intersection-observer polyfill
* - 使用标准的 intersection-observer polyfill,确保在所有浏览器中都能正常工作
*
* 核心特性:
* - 延迟执行:返回一个函数,调用时才开始执行定时检测
* - 定时检测:每隔指定时间间隔检测一次元素位置
* - 变化检测:只有当位置发生变化时才调用回调
* - 执行次数控制:可以指定执行次数,达到次数后自动停止
* - 立即执行:如果 interval 为 0,立即执行一次检测
* - 自动清理:组件卸载时自动清理定时器
*
* @param ref 要跟踪的元素的 ref
* @param options 配置选项
* - interval: 时间间隔(毫秒),默认 0(立即调用)
* - count: 执行次数,默认 1
* - callback: 回调函数,当位置变化时调用
* - 其他选项继承自 useLazyElementPositionRef
* @returns 返回一个函数,调用时才开始执行定时检测
*
* @example
* ```tsx
* const ref = useRef<HTMLDivElement>(null);
* const startDetection = useLazyElementPositionEffect(ref, {
* interval: 100, // 每 100ms 检测一次
* count: 10, // 执行 10 次
* callback: (position) => {
* if (position) {
* console.log('位置变化:', position.boundingClientRect);
* }
* },
* step: 0.1,
* throttle: 16,
* });
*
* // 在需要时开始检测
* const handleClick = () => {
* startDetection();
* };
* ```
*/
export declare const useLazyElementPositionEffect: (ref: React.RefObject<HTMLElement | null>, options: UseLazyElementPositionEffectOptions) => (() => void);
+4
-2

@@ -11,3 +11,4 @@ import type React from 'react';

* - 返回元素的交叉比例(0-1 或 undefined)
* - 支持所有 Intersection Observer 配置选项
* - 支持 threshold、offset、root、throttle 等配置选项
* - 内置节流机制,可控制更新频率
* - 自动处理比例更新和清理

@@ -26,3 +27,4 @@ * - 类型安全:支持 undefined 值处理

* step: 0.1, // 每 10% 触发一次
* throttle: 16 // 60fps
* throttle: 16, // 60fps
* offset: 50 // 50px 偏移
* });

@@ -29,0 +31,0 @@ *

@@ -8,2 +8,3 @@ import IntersectionLoad from './components/IntersectionLoad';

export { useLazyElementPositionRef } from './hooks/useLazyElementPositionRef';
export { useLazyElementPositionEffect } from './hooks/useLazyElementPositionEffect';
export { useElementDetector } from './hooks/useElementDetector';

@@ -10,0 +11,0 @@ export { useIntersectionObserver } from './hooks/useIntersectionObserver';

@@ -268,3 +268,3 @@ import { jsx } from "react/jsx-runtime";

const lazyloadManager = new IntersectionObserverManager();
const IntersectionLoad_IntersectionLoad = (props)=>{
const IntersectionLoad = (props)=>{
const { children, placeholder, threshold = 0.01, offset = 300, style, onChange, root = null } = props;

@@ -364,4 +364,4 @@ const [hasBeenVisible, setHasBeenVisible] = useState(false);

};
const MemoizedIntersectionLoad = memo(IntersectionLoad_IntersectionLoad);
const IntersectionLoad = MemoizedIntersectionLoad;
const MemoizedIntersectionLoad = memo(IntersectionLoad);
const components_IntersectionLoad = MemoizedIntersectionLoad;
const useIsMounted = ()=>{

@@ -815,2 +815,51 @@ const isMountedRef = useRef(true);

};
const useLazyElementPositionEffect = (ref, options)=>{
const getPosition = useLazyElementPositionRef(ref, options);
const interval = options.interval ?? 0;
const count = options.count ?? 1;
const callback = options.callback;
const lastPositionRef = useRef(null);
const executedCountRef = useRef(0);
const intervalIdRef = useRef(null);
const checkPosition = useCallback(()=>{
const currentPosition = getPosition();
const hasChanged = null === lastPositionRef.current || lastPositionRef.current !== currentPosition;
if (hasChanged && callback && currentPosition) callback(currentPosition);
lastPositionRef.current = currentPosition;
executedCountRef.current += 1;
if (executedCountRef.current >= count) {
if (null !== intervalIdRef.current) {
clearInterval(intervalIdRef.current);
intervalIdRef.current = null;
}
lastPositionRef.current = null;
executedCountRef.current = 0;
}
}, [
getPosition,
count,
callback
]);
const startDetection = useCallback(()=>{
if (null !== intervalIdRef.current) return;
lastPositionRef.current = null;
executedCountRef.current = 0;
if (0 === interval) return void checkPosition();
checkPosition();
if (executedCountRef.current < count) intervalIdRef.current = setInterval(()=>{
checkPosition();
}, interval);
}, [
interval,
count,
checkPosition
]);
useLayoutEffect(()=>()=>{
if (null !== intervalIdRef.current) {
clearInterval(intervalIdRef.current);
intervalIdRef.current = null;
}
}, []);
return startDetection;
};
const useElementDetector = (ref, options = {})=>{

@@ -990,2 +1039,5 @@ const [isConditionMet, setIsConditionMet] = useState(false);

const isMountedRef = useIsMounted();
const lastUpdateTimeRef = useRef(0);
const timeoutRef = useRef(null);
const throttle = options.throttle ?? 16;
const finalThreshold = useMemo(()=>calculateFinalThreshold(options, 'useIntersectionRatio'), [

@@ -1002,2 +1054,28 @@ options

]);
const throttledSetRatio = useCallback((newRatio)=>{
if (!isMountedRef.current) return;
const now = Date.now();
if (now - lastUpdateTimeRef.current >= throttle) {
setIntersectionRatio(newRatio);
lastUpdateTimeRef.current = now;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
} else {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(()=>{
if (isMountedRef.current) {
setIntersectionRatio(newRatio);
lastUpdateTimeRef.current = Date.now();
}
timeoutRef.current = null;
}, throttle - (now - lastUpdateTimeRef.current));
}
}, [
throttle,
isMountedRef
]);
const throttledSetRatioRef = useRef(throttledSetRatio);
throttledSetRatioRef.current = throttledSetRatio;
useLayoutEffect(()=>{

@@ -1007,3 +1085,3 @@ if (!ref.current) return;

if (!isMountedRef.current) return;
setIntersectionRatio(entry.intersectionRatio);
throttledSetRatioRef.current(entry.intersectionRatio);
};

@@ -1013,2 +1091,6 @@ const unsubscribe = lazyloadManager.observe(ref.current, callback, observerOptions);

if (unsubscribe) unsubscribe();
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};

@@ -1018,3 +1100,4 @@ }, [

observerOptions,
isMountedRef
isMountedRef,
throttledSetRatio
]);

@@ -1157,3 +1240,3 @@ return intersectionRatio;

};
const src = IntersectionLoad;
export { IntersectionLoad, lazyloadManager as IntersectionObserverManager, calculateFinalThreshold, calculateScrollBasedPosition, calculateScrollDirection, checkIfShouldSyncPosition, checkVisibility, createIntersectionObserver, src as default, generateThresholdArray, getDefaultThresholdArray, isSupportIntersectionObserver, uniqueId, useBoundingClientRect, useElementDetector, useElementPosition, useElementPositionRef, useInViewport, useIntersectionObserver, useIntersectionRatio, useIsMounted, useLazyElementPositionRef, useOneOffVisibility, useOneOffVisibilityEffect, useScrollDirection };
const src = components_IntersectionLoad;
export { components_IntersectionLoad as IntersectionLoad, lazyloadManager as IntersectionObserverManager, calculateFinalThreshold, calculateScrollBasedPosition, calculateScrollDirection, checkIfShouldSyncPosition, checkVisibility, createIntersectionObserver, src as default, generateThresholdArray, getDefaultThresholdArray, isSupportIntersectionObserver, uniqueId, useBoundingClientRect, useElementDetector, useElementPosition, useElementPositionRef, useInViewport, useIntersectionObserver, useIntersectionRatio, useIsMounted, useLazyElementPositionEffect, useLazyElementPositionRef, useOneOffVisibility, useOneOffVisibilityEffect, useScrollDirection };

@@ -330,2 +330,14 @@ import type React from 'react';

};
/**
* useLazyElementPositionEffect Hook 选项类型
* 扩展了基础的 Options 类型,增加了定时检测相关选项
*/
export type UseLazyElementPositionEffectOptions = Options & {
/** 时间间隔(毫秒),默认 0(立即调用) */
interval?: number;
/** 执行次数,默认 1 */
count?: number;
/** 回调函数,当位置变化时调用,接收 ElementPosition */
callback: (position: ElementPosition) => void;
};
export type CheckIfShouldSyncPositionResult = {

@@ -332,0 +344,0 @@ shouldCalibrate: boolean;

{
"name": "@fly4react/observer",
"version": "1.24.0",
"version": "1.25.0",
"description": "一个基于 Intersection Observer API 的现代 React 工具库,提供懒加载、可见性检测、位置跟踪和滚动方向检测功能",

@@ -73,3 +73,3 @@ "type": "module",

"@rsbuild/plugin-react": "^1.3.5",
"@rslib/core": "^0.14.0",
"@rslib/core": "^0.17.1",
"@testing-library/jest-dom": "^5.17.0",

@@ -76,0 +76,0 @@ "@testing-library/react": "^12.1.5",

+98
-22

@@ -45,12 +45,22 @@ # @fly4react/observer

import { useIntersectionObserver } from '@fly4react/observer';
import { useRef, useState } from 'react';
function MyComponent() {
const [ref, inView] = useIntersectionObserver({
threshold: 0.1,
rootMargin: '0px 0px -100px 0px'
});
const ref = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useIntersectionObserver(
ref,
(entry) => {
setIsVisible(entry.isIntersecting);
},
{
threshold: 0.1,
rootMargin: '0px 0px -100px 0px'
}
);
return (
<div ref={ref}>
{inView ? 'Element is visible!' : 'Element is not visible'}
{isVisible ? 'Element is visible!' : 'Element is not visible'}
</div>

@@ -68,13 +78,16 @@ );

```tsx
const [ref, inView, entry] = useIntersectionObserver(options, deps);
useIntersectionObserver(
ref: RefObject<HTMLElement | null>,
callback: (entry: ObserverCallbackParamType) => void,
options: ObserverOptions
): void
```
**Parameters:**
- `ref`: Ref to the target element
- `callback`: Callback function that receives the intersection entry
- `options`: IntersectionObserver options
- `deps`: Dependency array for memoization
**Returns:**
- `ref`: Ref to attach to target element
- `inView`: Boolean indicating if element is in view
- `entry`: IntersectionObserverEntry object
- `void` - This hook doesn't return anything, it calls the callback when intersection changes

@@ -86,3 +99,4 @@ ### useInViewport

```tsx
const [ref, inView] = useInViewport(options);
const ref = useRef<HTMLDivElement>(null);
const isInViewport = useInViewport(ref);
```

@@ -95,3 +109,4 @@

```tsx
const [ref, position] = useElementPosition(options);
const ref = useRef<HTMLDivElement>(null);
const position = useElementPosition(ref, options);
```

@@ -132,2 +147,38 @@

### useLazyElementPositionEffect
A hook for periodic position detection with change callbacks. Based on `useLazyElementPositionRef`, it adds scheduled detection functionality.
```tsx
const ref = useRef<HTMLDivElement>(null);
const startDetection = useLazyElementPositionEffect(ref, {
interval: 100, // Check every 100ms
count: 10, // Execute 10 times
callback: (position) => {
if (position) {
console.log('Position changed:', position.boundingClientRect);
}
},
step: 0.1,
throttle: 16,
});
// Start detection when needed
const handleClick = () => {
startDetection();
};
```
**Parameters:**
- `options.interval`: Time interval in milliseconds, default 0 (immediate call)
- `options.count`: Number of executions, default 1
- `options.callback`: Callback function called when position changes, receives `ElementPosition | null`
- Other options inherit from `useLazyElementPositionRef`
**Returns:**
- A function that starts periodic detection when called
- Detects element position every `interval` milliseconds
- Calls `callback` if position has changed
- Automatically stops after `count` executions
### useScrollDirection

@@ -138,3 +189,4 @@

```tsx
const scrollDirection = useScrollDirection();
const ref = useRef<HTMLDivElement>(null);
const { scrollDirection, isScrolling } = useScrollDirection(ref, options);
```

@@ -148,9 +200,19 @@

import { useIntersectionObserver } from '@fly4react/observer';
import { useRef, useState } from 'react';
function LazyImage({ src, alt }) {
const [ref, inView] = useIntersectionObserver({
threshold: 0.1,
triggerOnce: true
});
const ref = useRef<HTMLDivElement>(null);
const [inView, setInView] = useState(false);
useIntersectionObserver(
ref,
(entry) => {
setInView(entry.isIntersecting);
},
{
threshold: 0.1,
once: true
}
);
return (

@@ -172,5 +234,7 @@ <div ref={ref}>

import { useElementPosition } from '@fly4react/observer';
import { useRef } from 'react';
function ScrollIndicator() {
const [ref, position] = useElementPosition();
const ref = useRef<HTMLDivElement>(null);
const position = useElementPosition(ref);

@@ -180,3 +244,5 @@ return (

<div ref={ref}>Content</div>
<div>Position: {position.ratio}</div>
{position && (
<div>Position: {position.intersectionRatio}</div>
)}
</div>

@@ -217,8 +283,18 @@ );

import { useIntersectionObserver } from '@fly4react/observer';
import { useRef, useState } from 'react';
function AnimatedElement() {
const [ref, inView] = useIntersectionObserver({
threshold: 0.5
});
const ref = useRef<HTMLDivElement>(null);
const [inView, setInView] = useState(false);
useIntersectionObserver(
ref,
(entry) => {
setInView(entry.isIntersecting);
},
{
threshold: 0.5
}
);
return (

@@ -225,0 +301,0 @@ <div

@@ -540,3 +540,2 @@ # @fly4react/observer

throttle: 16, // 60fps
skipWhenOffscreen: true,
forceCalibrate: true, // 强制校准

@@ -582,3 +581,3 @@ calibrateInterval: 3000 // 每3秒校准一次

> **注意**:`useElementDetector` 是一个灵活的通用检测器,支持自定义计算逻辑和细致的 threshold 配置。默认检测元素是否贴顶(top ≤ 0),支持 step、threshold、throttle、skipWhenOffscreen、offset 等配置选项。
> **注意**:`useElementDetector` 是一个灵活的通用检测器,支持自定义计算逻辑和细致的 threshold 配置。默认检测元素是否贴顶(top ≤ 0),支持 step、threshold、throttle、offset 等配置选项。

@@ -625,3 +624,3 @@ #### useIsMounted

| `placeholder` | `ReactNode` | - | 占位符内容 |
| `threshold` | `number \| ThresholdType` | `0.1` | 触发阈值 |
| `threshold` | `number \| ThresholdType` | `0.01` | 触发阈值 |
| `offset` | `number` | `300` | 偏移量(像素) |

@@ -686,4 +685,3 @@ | `style` | `CSSProperties` | - | 容器样式 |

function useInViewport(
ref: RefObject<HTMLElement | null>,
options?: ViewportElementPositionOptions
ref: RefObject<HTMLElement | null>
): boolean

@@ -719,2 +717,44 @@ ```

#### useLazyElementPositionEffect
```tsx
function useLazyElementPositionEffect(
ref: RefObject<HTMLElement | null>,
options: UseLazyElementPositionEffectOptions
): () => void
```
**参数说明:**
- `options.interval`: 时间间隔(毫秒),默认 0(立即调用)
- `options.count`: 执行次数,默认 1
- `options.callback`: 回调函数,当位置变化时调用,接收 `ElementPosition | null`
- 其他选项继承自 `useLazyElementPositionRef`
**返回值说明:**
- 返回一个函数,调用时才开始执行定时检测
- 每隔 `interval` 时间检测一次元素位置
- 如果位置发生变化,调用 `callback`
- 执行 `count` 次后自动停止
**使用示例:**
```tsx
const ref = useRef<HTMLDivElement>(null);
const startDetection = useLazyElementPositionEffect(ref, {
interval: 100, // 每 100ms 检测一次
count: 10, // 执行 10 次
callback: (position) => {
if (position) {
console.log('位置变化:', position.boundingClientRect);
}
},
step: 0.1,
throttle: 16,
});
// 在需要时开始检测
const handleClick = () => {
startDetection();
};
```
#### useBoundingClientRect

@@ -754,3 +794,2 @@

- `options.throttle`: 节流时间(毫秒),控制更新频率
- `options.skipWhenOffscreen`: 元素完全不可见时跳过更新
- `options.offset`: 偏移量(像素)

@@ -820,3 +859,2 @@

throttle?: number;
skipWhenOffscreen?: boolean;
root?: RefObject<Element>;

@@ -823,0 +861,0 @@ relativeToRoot?: boolean;