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