Skip to content

Instantly share code, notes, and snippets.

@LawJolla
Created April 9, 2021 17:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LawJolla/7c3b500c55993edf4bbb86a85aeed1a1 to your computer and use it in GitHub Desktop.
Save LawJolla/7c3b500c55993edf4bbb86a85aeed1a1 to your computer and use it in GitHub Desktop.
NextJS Picture with Spinner
import * as React from "react"
import { SkeletonPicture } from "./SkeletonPicture"
import { AnimatePresence, motion } from "framer-motion"
import { AbsoluteSpinner } from "@wk/shared-ui"
import { default as NextImage, ImageProps } from "next/image"
import { myNextImgLoader } from "../pageComponents/vehicle/SwipeImages"
import { VehicleFragmentFragment } from "../../generated/graphql"
import { Unarray } from "../../interfaces/vehicles"
type IPictureWithSpinner = {
image?: Unarray<VehicleFragmentFragment["images"]>
sizes?: string
preload?: VehicleFragmentFragment["images"]
}
export const PictureWithSpinner = ({
image,
sizes = undefined,
preload = [],
...ImageProps
}: Omit<ImageProps, "src"> & IPictureWithSpinner) => {
const [initialImageSet, setInitialImageSet] = React.useState(false)
const [currentImg, setCurrentImg] = React.useState(image)
const [showImage, setShowImage] = React.useState(true)
const isLoadedRef = React.useRef(false)
const showImageRef = React.useRef(true)
React.useEffect(() => {
if (currentImg?.id !== image?.id) {
setCurrentImg(image)
setShowImage(true)
showImageRef.current = true
isLoadedRef.current = false
}
}, [currentImg, image, showImageRef.current, isLoadedRef.current])
React.useEffect(() => {
setTimeout(() => {
if (!isLoadedRef.current && showImageRef.current) {
setShowImage(false)
showImageRef.current = false
}
}, 500)
}, [isLoadedRef.current, showImageRef.current])
const onLoadCallback = (e) => {
if (e?.target?.srcset) {
isLoadedRef.current = true
setShowImage(true)
showImageRef.current = true
if (!initialImageSet) setInitialImageSet(true)
}
}
if (!currentImg) return <SkeletonPicture />
return (
<>
{!initialImageSet && (
<div className="absolute inset-0 pointer-events-none flex justify-center items-center z-20">
<SkeletonPicture />
</div>
)}
<AnimatePresence>
{initialImageSet && !showImage && (
<div className="absolute inset-0 z-10 pointer-events-none flex justify-center items-center">
<motion.div
initial={{ opacity: 0, scale: 0.3 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.3 }}
className="relative rounded-full w-32 h-32 max-h-[30%] max-w-[30%]"
style={{
backgroundColor: `rgba(255, 255, 255, 0.4)`,
backdropFilter: `blur(10px)`
}}
/>
<motion.div
initial={{ opacity: 0, scale: 0.3 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.3 }}
>
<AbsoluteSpinner />
</motion.div>
</div>
)}
</AnimatePresence>
<NextImage
loader={myNextImgLoader}
// @ts-expect-error fill type must not contain width or height but {...imageProps} may violate that rule
layout={`fill`}
src={currentImg.url}
objectFit="contain"
sizes={sizes}
onLoad={onLoadCallback}
{...ImageProps}
/>
<span className="absolute opacity-0 pointer-events-none">
{preload.map((img) => (
<NextImage
loader={myNextImgLoader}
layout={`fill`}
src={img.url}
objectFit="contain"
sizes={sizes}
/>
))}
</span>
</>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment