mirror of https://github.com/buster-so/buster.git
use in viewport
This commit is contained in:
parent
0fde170eee
commit
d898e7ecc0
|
@ -0,0 +1,64 @@
|
|||
import { DependencyList, EffectCallback, useEffect, useLayoutEffect } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useUnmount } from './useUnmount';
|
||||
import { depsAreSame } from '@/lib/depAreSame';
|
||||
import type { BasicTarget } from '@/lib/domTarget';
|
||||
import { getTargetElement } from '@/lib/domTarget';
|
||||
|
||||
const createEffectWithTarget = (useEffectType: typeof useEffect | typeof useLayoutEffect) => {
|
||||
/**
|
||||
*
|
||||
* @param effect
|
||||
* @param deps
|
||||
* @param target target should compare ref.current vs ref.current, dom vs dom, ()=>dom vs ()=>dom
|
||||
*/
|
||||
const useEffectWithTarget = (
|
||||
effect: EffectCallback,
|
||||
deps: DependencyList,
|
||||
target: BasicTarget<any> | BasicTarget<any>[]
|
||||
) => {
|
||||
const hasInitRef = useRef(false);
|
||||
|
||||
const lastElementRef = useRef<(Element | null)[]>([]);
|
||||
const lastDepsRef = useRef<DependencyList>([]);
|
||||
|
||||
const unLoadRef = useRef<any>();
|
||||
|
||||
useEffectType(() => {
|
||||
const targets = Array.isArray(target) ? target : [target];
|
||||
const els = targets.map((item) => getTargetElement(item));
|
||||
|
||||
// init run
|
||||
if (!hasInitRef.current) {
|
||||
hasInitRef.current = true;
|
||||
lastElementRef.current = els;
|
||||
lastDepsRef.current = deps;
|
||||
|
||||
unLoadRef.current = effect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
els.length !== lastElementRef.current.length ||
|
||||
!depsAreSame(lastElementRef.current, els) ||
|
||||
!depsAreSame(lastDepsRef.current, deps)
|
||||
) {
|
||||
unLoadRef.current?.();
|
||||
|
||||
lastElementRef.current = els;
|
||||
lastDepsRef.current = deps;
|
||||
unLoadRef.current = effect();
|
||||
}
|
||||
});
|
||||
|
||||
useUnmount(() => {
|
||||
unLoadRef.current?.();
|
||||
// for react-refresh
|
||||
hasInitRef.current = false;
|
||||
});
|
||||
};
|
||||
|
||||
return useEffectWithTarget;
|
||||
};
|
||||
|
||||
export const useEffectWithTarget = createEffectWithTarget(useEffect);
|
|
@ -0,0 +1,58 @@
|
|||
import 'intersection-observer';
|
||||
import { useState } from 'react';
|
||||
import type { BasicTarget } from '../lib/domTarget';
|
||||
import { getTargetElement } from '../lib/domTarget';
|
||||
import { useEffectWithTarget } from './useEffectWithTarget';
|
||||
|
||||
type CallbackType = (entry: IntersectionObserverEntry) => void;
|
||||
|
||||
export interface Options {
|
||||
rootMargin?: string;
|
||||
threshold?: number | number[];
|
||||
root?: BasicTarget<Element>;
|
||||
callback?: CallbackType;
|
||||
}
|
||||
|
||||
function useInViewport(target: BasicTarget | BasicTarget[], options?: Options) {
|
||||
const { callback, ...option } = options || {};
|
||||
|
||||
const [state, setState] = useState<boolean>();
|
||||
const [ratio, setRatio] = useState<number>();
|
||||
|
||||
useEffectWithTarget(
|
||||
() => {
|
||||
const targets = Array.isArray(target) ? target : [target];
|
||||
const els = targets.map((element) => getTargetElement(element)).filter(Boolean);
|
||||
|
||||
if (!els.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
setRatio(entry.intersectionRatio);
|
||||
setState(entry.isIntersecting);
|
||||
callback?.(entry);
|
||||
}
|
||||
},
|
||||
{
|
||||
...option,
|
||||
root: getTargetElement(options?.root)
|
||||
}
|
||||
);
|
||||
|
||||
els.forEach((el) => observer.observe(el!));
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
},
|
||||
[options?.rootMargin, options?.threshold, callback],
|
||||
target
|
||||
);
|
||||
|
||||
return [state, ratio] as const;
|
||||
}
|
||||
|
||||
export default useInViewport;
|
|
@ -0,0 +1,9 @@
|
|||
import type { DependencyList } from 'react';
|
||||
|
||||
export function depsAreSame(oldDeps: DependencyList, deps: DependencyList): boolean {
|
||||
if (oldDeps === deps) return true;
|
||||
for (let i = 0; i < oldDeps.length; i++) {
|
||||
if (!Object.is(oldDeps[i], deps[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import type { MutableRefObject } from 'react';
|
||||
import isFunction from 'lodash/isFunction';
|
||||
import isBrowser from './isBrowser';
|
||||
|
||||
type TargetValue<T> = T | undefined | null;
|
||||
|
||||
type TargetType = HTMLElement | Element | Window | Document;
|
||||
|
||||
export type BasicTarget<T extends TargetType = Element> =
|
||||
| (() => TargetValue<T>)
|
||||
| TargetValue<T>
|
||||
| MutableRefObject<TargetValue<T>>;
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export const isBrowser = !!(
|
||||
typeof window !== 'undefined' &&
|
||||
window.document &&
|
||||
window.document.createElement
|
||||
);
|
||||
|
||||
export default isBrowser;
|
Loading…
Reference in New Issue