mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
optimize skeleton animation (#1832)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { type CSSProperties, memo } from 'react';
|
import { type CSSProperties, memo, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import styles from './skeleton.module.css';
|
import styles from './skeleton.module.css';
|
||||||
|
|
||||||
@@ -32,6 +32,64 @@ export function BaseSkeleton({
|
|||||||
style,
|
style,
|
||||||
width,
|
width,
|
||||||
}: SkeletonProps) {
|
}: SkeletonProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [isInViewport, setIsInViewport] = useState(false);
|
||||||
|
const [isDocumentVisible, setIsDocumentVisible] = useState(
|
||||||
|
typeof document === 'undefined' ? true : document.visibilityState === 'visible',
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enableAnimation || typeof document === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
setIsDocumentVisible(document.visibilityState === 'visible');
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
};
|
||||||
|
}, [enableAnimation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enableAnimation) {
|
||||||
|
setIsInViewport(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = containerRef.current;
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof IntersectionObserver === 'undefined') {
|
||||||
|
setIsInViewport(true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
const [entry] = entries;
|
||||||
|
setIsInViewport(Boolean(entry?.isIntersecting));
|
||||||
|
},
|
||||||
|
{ threshold: 0.01 },
|
||||||
|
);
|
||||||
|
|
||||||
|
observer.observe(element);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, [enableAnimation, count, inline, isCentered, direction]);
|
||||||
|
|
||||||
|
const shouldAnimate = enableAnimation && isDocumentVisible && isInViewport;
|
||||||
|
|
||||||
const skeletonStyle: CSSProperties = {
|
const skeletonStyle: CSSProperties = {
|
||||||
...style,
|
...style,
|
||||||
...(baseColor && { ['--base-color' as string]: baseColor }),
|
...(baseColor && { ['--base-color' as string]: baseColor }),
|
||||||
@@ -49,19 +107,23 @@ export function BaseSkeleton({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const skeletonClasses = clsx(styles.skeleton, className, {
|
const skeletonClasses = clsx(styles.skeleton, className, {
|
||||||
[styles.animated]: enableAnimation,
|
[styles.animated]: shouldAnimate,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (count <= 1) {
|
if (count <= 1) {
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses} ref={containerRef}>
|
||||||
<div className={skeletonClasses} style={skeletonStyle} />
|
<div className={skeletonClasses} style={skeletonStyle} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(containerClasses, styles.skeletonWrapper)} dir={direction}>
|
<div
|
||||||
|
className={clsx(containerClasses, styles.skeletonWrapper)}
|
||||||
|
dir={direction}
|
||||||
|
ref={containerRef}
|
||||||
|
>
|
||||||
{Array.from({ length: count }, (_, i) => (
|
{Array.from({ length: count }, (_, i) => (
|
||||||
<div className={skeletonClasses} key={i} style={skeletonStyle} />
|
<div className={skeletonClasses} key={i} style={skeletonStyle} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user