# 摄影作品集网站 - 前端开发文档
## 1. 技术架构
### 1.1 技术栈选择
- **框架**:Next.js 14 (App Router)
- **样式**:Tailwind CSS + CSS Modules
- **状态管理**:Zustand + React Query
- **图片处理**:Sharp + Next.js Image
- **动画**:Framer Motion
- **UI组件**:Headless UI + 自定义组件
### 1.2 项目结构
```
src/
├── app/ # App Router页面
│ ├── layout.tsx # 根布局
│ ├── page.tsx # 首页
│ ├── gallery/ # 作品集页面
│ ├── timeline/ # 时间线页面
│ └── about/ # 关于页面
├── components/ # 可复用组件
│ ├── ui/ # 基础UI组件
│ ├── gallery/ # 作品集相关组件
│ ├── timeline/ # 时间线组件
│ └── layout/ # 布局组件
├── hooks/ # 自定义Hooks
├── lib/ # 工具函数
├── stores/ # 状态管理
├── types/ # TypeScript类型
└── styles/ # 全局样式
```
## 2. 核心功能实现
### 2.1 图片管理系统
#### 多格式图片处理
```typescript
// lib/imageUtils.ts
export interface ImageFormats {
raw?: string; // RAW文件路径
jpg: string; // JPG文件路径
webp?: string; // WebP优化版本
thumb: string; // 缩略图
}
export interface PhotoData {
id: string;
title: string;
description?: string;
date: string;
formats: ImageFormats;
metadata: {
camera?: string;
lens?: string;
settings?: {
iso: number;
aperture: string;
shutter: string;
focal: string;
};
};
collections: string[];
}
```
#### 响应式图片组件
```typescript
// components/ui/ResponsiveImage.tsx
interface ResponsiveImageProps {
photo: PhotoData;
sizes: string;
priority?: boolean;
className?: string;
onClick?: () => void;
}
export function ResponsiveImage({ photo, sizes, priority, className, onClick }: ResponsiveImageProps) {
return (
);
}
```
### 2.2 作品集网格系统
#### 自适应网格布局
```typescript
// components/gallery/PhotoGrid.tsx
export function PhotoGrid({ photos }: { photos: PhotoData[] }) {
return (
{photos.map((photo, index) => (
openLightbox(photo)}
/>
))}
);
}
```
#### 虚拟化长列表
```typescript
// hooks/useVirtualGrid.ts
export function useVirtualGrid(items: PhotoData[], itemHeight: number) {
const [visibleItems, setVisibleItems] = useState([]);
const [startIndex, setStartIndex] = useState(0);
const handleScroll = useCallback((scrollTop: number, containerHeight: number) => {
const newStartIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
newStartIndex + Math.ceil(containerHeight / itemHeight) + 2,
items.length
);
setStartIndex(newStartIndex);
setVisibleItems(items.slice(newStartIndex, endIndex));
}, [items, itemHeight]);
return { visibleItems, startIndex, handleScroll };
}
```
### 2.3 时间线功能
#### 时间线数据结构
```typescript
// types/timeline.ts
export interface TimelineGroup {
year: number;
months: {
[month: number]: {
photos: PhotoData[];
count: number;
};
};
}
export interface TimelineData {
groups: TimelineGroup[];
totalPhotos: number;
yearRange: [number, number];
}
```
#### 时间线组件
```typescript
// components/timeline/Timeline.tsx
export function Timeline({ data }: { data: TimelineData }) {
const [selectedYear, setSelectedYear] = useState(data.yearRange[1]);
return (
{/* 年份导航 */}
g.year)}
selectedYear={selectedYear}
onYearSelect={setSelectedYear}
/>
{/* 时间线内容 */}
{data.groups
.filter(group => group.year === selectedYear)
.map(group => (
))}
);
}
```
### 2.4 灯箱组件
#### 全功能图片查看器
```typescript
// components/ui/Lightbox.tsx
export function Lightbox() {
const { isOpen, currentPhoto, photos, currentIndex } = useLightboxStore();
const [isZoomed, setIsZoomed] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
if (!isOpen || !currentPhoto) return null;
return (
{/* 图片区域 */}
{/* 导航控件 */}
{/* 信息面板 */}
);
}
```
## 3. 性能优化策略
### 3.1 图片优化
#### 渐进式加载
```typescript
// hooks/useProgressiveImage.ts
export function useProgressiveImage(src: string, placeholderSrc: string) {
const [currentSrc, setCurrentSrc] = useState(placeholderSrc);
const [loading, setLoading] = useState(true);
useEffect(() => {
const img = new Image();
img.src = src;
img.onload = () => {
setCurrentSrc(src);
setLoading(false);
};
}, [src]);
return { src: currentSrc, loading };
}
```
#### 智能预加载
```typescript
// hooks/useImagePreloader.ts
export function useImagePreloader(photos: PhotoData[], currentIndex: number) {
useEffect(() => {
// 预加载当前图片的前后各2张
const preloadRange = 2;
const start = Math.max(0, currentIndex - preloadRange);
const end = Math.min(photos.length, currentIndex + preloadRange + 1);
for (let i = start; i < end; i++) {
if (i !== currentIndex) {
const img = new Image();
img.src = photos[i].formats.jpg;
}
}
}, [photos, currentIndex]);
}
```
### 3.2 状态管理优化
#### Zustand Store设计
```typescript
// stores/galleryStore.ts
interface GalleryState {
photos: PhotoData[];
collections: Collection[];
currentCollection: string | null;
loading: boolean;
error: string | null;
// Actions
setPhotos: (photos: PhotoData[]) => void;
setCurrentCollection: (id: string | null) => void;
addPhotos: (photos: PhotoData[]) => void;
}
export const useGalleryStore = create((set, get) => ({
photos: [],
collections: [],
currentCollection: null,
loading: false,
error: null,
setPhotos: (photos) => set({ photos }),
setCurrentCollection: (id) => set({ currentCollection: id }),
addPhotos: (newPhotos) => set((state) => ({
photos: [...state.photos, ...newPhotos]
})),
}));
```
### 3.3 代码分割
#### 动态导入
```typescript
// app/gallery/page.tsx
const LazyPhotoGrid = dynamic(() => import('@/components/gallery/PhotoGrid'), {
loading: () => ,
ssr: false
});
const LazyLightbox = dynamic(() => import('@/components/ui/Lightbox'), {
ssr: false
});
```
## 4. 移动端优化
### 4.1 触摸手势
```typescript
// hooks/useSwipeGesture.ts
export function useSwipeGesture(onSwipeLeft: () => void, onSwipeRight: () => void) {
const touchStart = useRef<{ x: number; y: number } | null>(null);
const touchEnd = useRef<{ x: number; y: number } | null>(null);
const handleTouchStart = (e: TouchEvent) => {
touchEnd.current = null;
touchStart.current = {
x: e.targetTouches[0].clientX,
y: e.targetTouches[0].clientY,
};
};
const handleTouchEnd = () => {
if (!touchStart.current || !touchEnd.current) return;
const distance = touchStart.current.x - touchEnd.current.x;
const isLeftSwipe = distance > 50;
const isRightSwipe = distance < -50;
if (isLeftSwipe) onSwipeLeft();
if (isRightSwipe) onSwipeRight();
};
return { handleTouchStart, handleTouchEnd };
}
```
### 4.2 移动端导航
```typescript
// components/layout/MobileNavigation.tsx
export function MobileNavigation() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
{/* 汉堡菜单按钮 */}
{/* 侧边导航 */}
{isOpen && (
)}
>
);
}
```
## 5. SEO优化
### 5.1 元数据管理
```typescript
// app/gallery/[collection]/page.tsx
export async function generateMetadata({ params }: { params: { collection: string } }) {
const collection = await getCollection(params.collection);
return {
title: `${collection.name} | 摄影作品集`,
description: collection.description,
openGraph: {
title: collection.name,
description: collection.description,
images: [collection.coverImage],
},
};
}
```
### 5.2 结构化数据
```typescript
// components/seo/StructuredData.tsx
export function PhotoStructuredData({ photo }: { photo: PhotoData }) {
const structuredData = {
'@context': 'https://schema.org',
'@type': 'Photograph',
name: photo.title,
description: photo.description,
contentUrl: photo.formats.jpg,
thumbnail: photo.formats.thumb,
dateCreated: photo.date,
creator: {
'@type': 'Person',
name: '摄影师姓名',
},
};
return (
);
}
```
## 6. 部署和监控
### 6.1 构建优化
```javascript
// next.config.js
module.exports = {
images: {
domains: ['your-domain.com'],
formats: ['image/webp', 'image/avif'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256],
},
experimental: {
optimizeCss: true,
optimizePackageImports: ['framer-motion'],
},
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
};
```
### 6.2 性能监控
```typescript
// lib/analytics.ts
export function trackPageView(url: string) {
if (typeof window !== 'undefined') {
gtag('config', 'GA_TRACKING_ID', {
page_location: url,
});
}
}
export function trackPhotoView(photoId: string) {
gtag('event', 'photo_view', {
event_category: 'engagement',
event_label: photoId,
});
}
```
## 7. 开发工具配置
### 7.1 TypeScript配置
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"]
}
}
}
```
### 7.2 代码质量工具
```json
// package.json
{
"scripts": {
"lint": "next lint",
"lint:fix": "next lint --fix",
"type-check": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch"
}
}
```