optimize image component

- use new intersection hooks instead of react-intersection-observer
- remove motion, replace with css animation
- remove unneeded container from Loader component
This commit is contained in:
jeffvli 2025-11-01 05:00:51 -07:00
parent 29991ea95d
commit 805c75a67f
2 changed files with 51 additions and 46 deletions

View file

@ -1,3 +1,13 @@
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.image { .image {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -5,6 +15,11 @@
border-radius: var(--theme-radius-md); border-radius: var(--theme-radius-md);
} }
.image.animated {
opacity: 1;
animation: fade-in 0.2s ease-in;
}
.loader { .loader {
width: 100%; width: 100%;
height: 100%; height: 100%;

View file

@ -1,14 +1,13 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { motion, MotionConfigProps } from 'motion/react'; import { MotionConfigProps } from 'motion/react';
import { ForwardedRef, forwardRef, type ImgHTMLAttributes } from 'react'; import { ForwardedRef, forwardRef, type ImgHTMLAttributes } from 'react';
import { Img } from 'react-image'; import { Img } from 'react-image';
import { InView } from 'react-intersection-observer';
import styles from './image.module.css'; import styles from './image.module.css';
import { animationProps } from '/@/shared/components/animations/animation-props';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { useInViewport } from '/@/shared/hooks/use-in-viewport';
interface ImageContainerProps extends MotionConfigProps { interface ImageContainerProps extends MotionConfigProps {
children: React.ReactNode; children: React.ReactNode;
@ -46,40 +45,40 @@ export function Image({
includeUnloader = true, includeUnloader = true,
src, src,
}: ImageProps) { }: ImageProps) {
const { inViewport, ref } = useInViewport();
if (src) { if (src) {
return ( return (
<InView> <Img
{({ inView, ref }) => ( className={clsx(styles.image, className, {
<Img [styles.animated]: enableAnimation,
className={clsx(styles.image, className)} })}
container={(children) => ( container={(children) => (
<ImageContainer <ImageContainer
className={containerClassName} className={containerClassName}
enableAnimation={enableAnimation} enableAnimation={enableAnimation}
ref={ref} ref={ref}
{...imageContainerProps} {...imageContainerProps}
> >
{children} {children}
</ImageContainer> </ImageContainer>
)}
loader={
includeLoader ? (
<ImageContainer className={containerClassName}>
<ImageLoader className={className} />
</ImageContainer>
) : null
}
src={inView ? src : FALLBACK_SVG}
unloader={
includeUnloader ? (
<ImageContainer className={containerClassName}>
<ImageUnloader className={className} />
</ImageContainer>
) : null
}
/>
)} )}
</InView> loader={
includeLoader ? (
<ImageContainer className={containerClassName}>
<ImageLoader className={className} />
</ImageContainer>
) : null
}
src={inViewport ? src : FALLBACK_SVG}
unloader={
includeUnloader ? (
<ImageContainer className={containerClassName}>
<ImageUnloader className={className} />
</ImageContainer>
) : null
}
/>
); );
} }
@ -100,24 +99,15 @@ const ImageContainer = forwardRef(
} }
return ( return (
<motion.div <div className={clsx(styles.imageContainer, className)} ref={ref} {...props}>
className={clsx(styles.imageContainer, className)}
ref={ref}
{...animationProps.fadeIn}
{...props}
>
{children} {children}
</motion.div> </div>
); );
}, },
); );
function ImageLoader({ className }: ImageLoaderProps) { function ImageLoader({ className }: ImageLoaderProps) {
return ( return <Skeleton className={clsx(styles.skeleton, styles.loader, className)} />;
<div className={clsx(styles.loader, className)}>
<Skeleton className={clsx(styles.skeleton, className)} enableAnimation={true} />
</div>
);
} }
function ImageUnloader({ className }: ImageUnloaderProps) { function ImageUnloader({ className }: ImageUnloaderProps) {