136 lines
3.2 KiB
TypeScript
Raw Normal View History

2022-05-29 17:53:27 +08:00
import { css, cx } from '@emotion/css'
import { AnimatePresence, motion, useAnimation } from 'framer-motion'
2022-06-11 00:19:07 +08:00
import { useEffect, useState } from 'react'
2022-05-29 17:53:27 +08:00
import { ease } from '@/web/utils/const'
2022-06-11 00:19:07 +08:00
import useIsMobile from '@/web/hooks/useIsMobile'
2022-05-29 17:53:27 +08:00
2022-06-14 23:23:34 +08:00
type Props = {
2022-05-29 17:53:27 +08:00
src?: string
srcSet?: string
sizes?: string
className?: string
lazyLoad?: boolean
2022-06-12 15:29:14 +08:00
placeholder?: 'artist' | 'album' | 'playlist' | 'podcast' | 'blank' | false
2022-05-29 17:53:27 +08:00
onClick?: (e: React.MouseEvent<HTMLImageElement>) => void
2022-06-06 01:00:25 +08:00
onMouseOver?: (e: React.MouseEvent<HTMLImageElement>) => void
2022-06-11 00:19:07 +08:00
animation?: boolean
2022-06-14 23:23:34 +08:00
}
const ImageDesktop = ({
src,
srcSet,
className,
lazyLoad = true,
sizes,
placeholder = 'blank',
onClick,
onMouseOver,
animation = true,
}: Props) => {
2022-05-29 17:53:27 +08:00
const [error, setError] = useState(false)
const animate = useAnimation()
2022-06-14 23:23:34 +08:00
const placeholderAnimate = useAnimation()
2022-06-11 00:19:07 +08:00
const isMobile = useIsMobile()
const isAnimate = animation && !isMobile
2022-05-29 17:53:27 +08:00
useEffect(() => setError(false), [src])
2022-06-11 00:19:07 +08:00
const onLoad = async () => {
2022-06-14 23:23:34 +08:00
if (isAnimate) {
animate.start({ opacity: 1 })
placeholderAnimate.start({ opacity: 0 })
}
2022-05-29 17:53:27 +08:00
}
const onError = () => {
setError(true)
}
2022-06-11 00:19:07 +08:00
const transition = { duration: 0.6, ease }
const motionProps = isAnimate
? {
animate,
initial: { opacity: 0 },
2022-06-12 15:29:14 +08:00
exit: { opacity: 0 },
2022-06-11 00:19:07 +08:00
transition,
}
: {}
const placeholderMotionProps = isAnimate
? {
2022-06-14 23:23:34 +08:00
animate: placeholderAnimate,
2022-06-11 00:19:07 +08:00
initial: { opacity: 1 },
exit: { opacity: 0 },
transition,
}
: {}
2022-05-29 17:53:27 +08:00
return (
2022-06-12 15:29:14 +08:00
<div
2022-06-14 23:23:34 +08:00
onClick={onClick}
onMouseOver={onMouseOver}
2022-06-12 15:29:14 +08:00
className={cx(
'overflow-hidden',
className,
className?.includes('absolute') === false && 'relative'
)}
>
2022-05-29 17:53:27 +08:00
{/* Image */}
2022-06-12 15:29:14 +08:00
<AnimatePresence>
<motion.img
className='absolute inset-0 h-full w-full'
src={src}
srcSet={srcSet}
sizes={sizes}
decoding='async'
loading={lazyLoad ? 'lazy' : undefined}
onError={onError}
onLoad={onLoad}
{...motionProps}
/>
</AnimatePresence>
2022-05-29 17:53:27 +08:00
{/* Placeholder / Error fallback */}
<AnimatePresence>
2022-06-14 23:23:34 +08:00
{placeholder && (
2022-05-29 17:53:27 +08:00
<motion.div
2022-06-11 00:19:07 +08:00
{...placeholderMotionProps}
2022-08-03 23:48:39 +08:00
className='absolute inset-0 h-full w-full bg-white dark:bg-white/10'
2022-05-29 17:53:27 +08:00
></motion.div>
)}
</AnimatePresence>
</div>
)
}
2022-06-14 23:23:34 +08:00
const ImageMobile = (props: Props) => {
const { src, className, srcSet, sizes, lazyLoad, onClick, onMouseOver } =
props
return (
<div
onClick={onClick}
onMouseOver={onMouseOver}
className={cx(
'overflow-hidden',
className,
className?.includes('absolute') === false && 'relative'
)}
>
{src && (
<img
className='absolute inset-0 h-full w-full'
src={src}
srcSet={srcSet}
sizes={sizes}
decoding='async'
loading={lazyLoad ? 'lazy' : undefined}
/>
)}
</div>
)
}
const Image = (props: Props) => {
const isMobile = useIsMobile()
return isMobile ? <ImageMobile {...props} /> : <ImageDesktop {...props} />
}
2022-05-29 17:53:27 +08:00
export default Image