first commit
This commit is contained in:
103
components/ui/OptimizedImage.tsx
Normal file
103
components/ui/OptimizedImage.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
import { useState } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface OptimizedImageProps {
|
||||
src: string
|
||||
alt: string
|
||||
width: number
|
||||
height: number
|
||||
className?: string
|
||||
priority?: boolean
|
||||
fill?: boolean
|
||||
sizes?: string
|
||||
quality?: number
|
||||
placeholder?: 'blur' | 'empty'
|
||||
blurDataURL?: string
|
||||
}
|
||||
|
||||
// Generate a simple blur placeholder
|
||||
const shimmer = (w: number, h: number) => `
|
||||
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="g">
|
||||
<stop stop-color="#f3f4f6" offset="20%" />
|
||||
<stop stop-color="#e5e7eb" offset="50%" />
|
||||
<stop stop-color="#f3f4f6" offset="70%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="${w}" height="${h}" fill="#f3f4f6" />
|
||||
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
|
||||
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||
</svg>`
|
||||
|
||||
const toBase64 = (str: string) =>
|
||||
typeof window === 'undefined'
|
||||
? Buffer.from(str).toString('base64')
|
||||
: window.btoa(str)
|
||||
|
||||
export default function OptimizedImage({
|
||||
src,
|
||||
alt,
|
||||
width,
|
||||
height,
|
||||
className,
|
||||
priority = false,
|
||||
fill = false,
|
||||
sizes,
|
||||
quality = 75,
|
||||
placeholder = 'blur',
|
||||
blurDataURL,
|
||||
...props
|
||||
}: OptimizedImageProps) {
|
||||
const [imageError, setImageError] = useState(false)
|
||||
const [imageLoaded, setImageLoaded] = useState(false)
|
||||
|
||||
// Default fallback image for products
|
||||
const fallbackSrc = '/logo.png'
|
||||
|
||||
// Generate blur placeholder if not provided
|
||||
const defaultBlurDataURL = `data:image/svg+xml;base64,${toBase64(shimmer(width, height))}`
|
||||
|
||||
const handleError = () => {
|
||||
setImageError(true)
|
||||
}
|
||||
|
||||
const handleLoad = () => {
|
||||
setImageLoaded(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("relative overflow-hidden", className)}>
|
||||
<Image
|
||||
src={imageError ? fallbackSrc : src}
|
||||
alt={alt}
|
||||
width={fill ? undefined : width}
|
||||
height={fill ? undefined : height}
|
||||
fill={fill}
|
||||
priority={priority}
|
||||
quality={quality}
|
||||
sizes={sizes || fill ? '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw' : undefined}
|
||||
placeholder={placeholder}
|
||||
blurDataURL={blurDataURL || defaultBlurDataURL}
|
||||
className={cn(
|
||||
"transition-all duration-300",
|
||||
imageLoaded ? "opacity-100" : "opacity-0",
|
||||
fill ? "object-cover" : "w-full h-full object-cover"
|
||||
)}
|
||||
onError={handleError}
|
||||
onLoad={handleLoad}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
{/* Loading shimmer effect */}
|
||||
{!imageLoaded && (
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-gray-200 via-gray-100 to-gray-200 animate-pulse">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent animate-shimmer"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user