Files
novalon-website/src/components/ui/optimized-image.tsx
T
张翔 1f591fe2b4 feat: 完成网站功能开发与优化
- 完善产品页面布局与交互
- 优化服务详情页用户体验
- 增强新闻模块内容展示
- 改进团队页面设计
- 优化全局样式和响应式布局
- 添加分页组件支持
- 提升性能与SEO优化
- 修复已知问题与改进代码质量
2026-04-27 20:53:39 +08:00

113 lines
2.9 KiB
TypeScript

'use client';
import { useState, useCallback } from 'react';
import Image from 'next/image';
import { cn } from '@/lib/utils';
interface OptimizedImageProps {
src: string;
alt: string;
width?: number;
height?: number;
fill?: boolean;
className?: string;
containerClassName?: string;
priority?: boolean;
sizes?: string;
quality?: number;
placeholder?: 'blur' | 'empty';
blurDataURL?: string;
}
export function OptimizedImage({
src,
alt,
width,
height,
fill = false,
className,
containerClassName,
priority = false,
sizes = '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw',
quality = 85,
placeholder = 'blur',
blurDataURL,
}: OptimizedImageProps) {
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(false);
// 生成默认的模糊占位符
const defaultBlurDataURL = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iNDAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjFmNWY5Ii8+PC9zdmc+';
// 使用 callback 来处理加载状态
const handleLoad = useCallback(() => {
setIsLoaded(true);
}, []);
const handleError = useCallback(() => {
setError(true);
}, []);
if (error) {
return (
<div
className={cn(
'bg-gray-100 flex items-center justify-center',
containerClassName
)}
style={!fill ? { width, height } : undefined}
>
<span className="text-gray-400 text-sm"></span>
</div>
);
}
return (
<div
className={cn(
'relative overflow-hidden',
fill ? 'w-full h-full' : '',
containerClassName
)}
style={!fill ? { width, height } : undefined}
>
{/* 模糊占位符背景 */}
{!isLoaded && placeholder === 'blur' && (
<div
className="absolute inset-0 bg-cover bg-center blur-sm scale-110 transition-opacity duration-500"
style={{
backgroundImage: `url(${blurDataURL || defaultBlurDataURL})`,
}}
/>
)}
{/* 加载动画 */}
{!isLoaded && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-8 h-8 border-2 border-gray-200 border-t-[var(--color-brand-primary)] rounded-full animate-spin" />
</div>
)}
<Image
src={src}
alt={alt}
width={fill ? undefined : width}
height={fill ? undefined : height}
fill={fill}
priority={priority}
sizes={sizes}
quality={quality}
placeholder={placeholder}
blurDataURL={blurDataURL || defaultBlurDataURL}
className={cn(
'transition-opacity duration-500',
isLoaded ? 'opacity-100' : 'opacity-0',
className
)}
onLoad={handleLoad}
onError={handleError}
/>
</div>
);
}