<!-- Generated by SpecStory -->

# 使用 blurhash 作为图片占位符 (2025-06-04 14:31:54)

_**User**_

这里在 image 没有加载出来或者加载失败需要使用 blurhash 替代显示

---

_**Assistant**_

我看到您需要在图片加载失败或还没加载完成时使用 blurhash 作为占位符显示。我需要先了解一下 PhotoManifest 类型的结构，看看是否包含 blurhash 信息。

---

<details>
            <summary>Searched codebase "PhotoManifest" • **1** results</summary>
        
| File |
|------|
| `undefined` |

</details>

---

Read file: apps/web/src/types/photo.ts

---

太好了！我看到 `PhotoManifest` 类型确实包含 `blurhash` 字段。现在我需要检查项目中是否已经有 blurhash 解码的相关组件或库。

---

<details>
            <summary>Searched codebase "blurhash BlurhashCanvas BlurhashImage" • **25** results</summary>
        
| File | Lines |
|------|-------|
| `apps/web/src/core/image/blurhash.ts` | L1-59 |
| `apps/web/src/pages/(debug)/blurhash.tsx` | L1-39 |
| `apps/web/src/core/image/blurhash.ts` | L59-76 |
| `apps/web/src/core/index.ts` | L26-29 |
| `apps/web/src/modules/gallery/PhotoMasonryItem.tsx` | L1-30 |
| `apps/web/src/core/types/photo.ts` | L42-48 |
| `apps/web/src/core/image/thumbnail.ts` | L27-91 |
| `apps/web/src/core/index.ts` | L34-38 |
| `apps/web/src/core/image/thumbnail.ts` | L91-121 |
| `apps/web/src/modules/gallery/PhotoMasonryItem.tsx` | L232-295 |
| `apps/web/src/core/image/thumbnail.ts` | L1-27 |
| `apps/web/src/core/photo/processor.ts` | L130-181 |
| `scripts/photo-loader.ts` | L1-21 |
| `apps/web/src/types/photo.ts` | L1-23 |
| `packages/webgl-viewer/src/WebGLImageViewerEngine.ts` | L1-62 |
| `packages/webgl-viewer/src/WebGLImageViewer.tsx` | L106-166 |
| `apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L181-251 |
| `apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L1-29 |
| `apps/web/src/core/photo/processor.ts` | L1-27 |
| `apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L43-119 |
| `apps/web/src/data/photos-manifest.json` | L4706-4740 |
| `apps/web/src/data/photos-manifest.json` | L2110-2157 |
| `packages/webgl-viewer/src/WebGLImageViewer.tsx` | L48-106 |
| `apps/web/src/core/photo/processor.ts` | L92-130 |
| `apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L154-184 |

</details>

---

很好！项目已经有了 `react-blurhash` 库，并且在其他组件中已经使用了。现在我需要修改 `GalleryThumbnail.tsx` 来添加 blurhash 占位符功能。我需要：

1. 添加图片加载状态管理
2. 在图片加载失败或加载中时显示 blurhash
3. 使用 `react-blurhash` 库

---

```diff
  import { m } from 'motion/react'
  import type { FC } from 'react'
- import { useEffect, useRef } from 'react'
+ import { useEffect, useRef, useState } from 'react'
+ import { Blurhash } from 'react-blurhash'
  
  import { useMobile } from '~/hooks/useMobile'
```

```diff
    const thumbnailRefs = useRef<(HTMLButtonElement | null)[]>([])
    const isMobile = useMobile()
+ 
+   // 管理每张图片的加载状态
+   const [imageLoadStates, setImageLoadStates] = useState<{
+     [key: string]: { loaded: boolean; error: boolean }
+   }>({})
+ 
+   // 处理图片加载完成
+   const handleImageLoad = (photoId: string) => {
+     setImageLoadStates(prev => ({
+       ...prev,
+       [photoId]: { loaded: true, error: false }
+     }))
+   }
+ 
+   // 处理图片加载失败
+   const handleImageError = (photoId: string) => {
+     setImageLoadStates(prev => ({
+       ...prev,
+       [photoId]: { loaded: false, error: true }
+     }))
+   }
+ 
+   // 检查图片是否应该显示 blurhash
+   const shouldShowBlurhash = (photoId: string) => {
+     const state = imageLoadStates[photoId]
+     return !state || !state.loaded || state.error
+   }
  
    useEffect(() => {
```

```diff
                }}
                className={clsxm(
-                 `flex-shrink-0 ${isMobile ? 'w-12 h-12' : 'w-16 h-16'} rounded-lg overflow-hidden ring-2 transition-all contain-intrinsic-size`,
+                 `flex-shrink-0 ${isMobile ? 'w-12 h-12' : 'w-16 h-16'} rounded-lg overflow-hidden ring-2 transition-all contain-intrinsic-size relative`,
                  index === currentIndex
                    ? 'ring-accent scale-110'
```

```diff
                onClick={() => onIndexChange(index)}
              >
+               {/* Blurhash 占位符 */}
+               {shouldShowBlurhash(photo.id) && photo.blurhash && (
+                 <Blurhash
+                   hash={photo.blurhash}
+                   width="100%"
+                   height="100%"
+                   resolutionX={32}
+                   resolutionY={32}
+                   punch={1}
+                   className="absolute inset-0"
+                 />
+               )}
+               
+               {/* 实际图片 */}
                <img
                  src={photo.thumbnailUrl}
                  alt={photo.title}
-                 className="h-full w-full object-cover"
+                 className={clsxm(
+                   "h-full w-full object-cover transition-opacity duration-300",
+                   shouldShowBlurhash(photo.id) ? "opacity-0" : "opacity-100"
+                 )}
+                 onLoad={() => handleImageLoad(photo.id)}
+                 onError={() => handleImageError(photo.id)}
                />
              </button>
```

---

完美！我已经成功为 `GalleryThumbnail` 组件添加了 blurhash 占位符功能。让我总结一下所做的改动：

## 主要改动

1. **添加了必要的导入**：
   - 导入了 `useState` hook 用于状态管理
   - 导入了 `react-blurhash` 库中的 `Blurhash` 组件

2. **添加了图片加载状态管理**：
   - 使用 `imageLoadStates` 状态来跟踪每张图片的加载状态
   - 包含 `loaded` 和 `error` 两个状态字段

3. **添加了事件处理函数**：
   - `handleImageLoad`: 处理图片加载成功的情况
   - `handleImageError`: 处理图片加载失败的情况
   - `shouldShowBlurhash`: 判断是否应该显示 blurhash 占位符

4. **修改了缩略图的渲染结构**：
   - 给按钮添加了 `relative` 定位，为绝对定位的 blurhash 提供容器
   - 添加了 blurhash 组件作为占位符，当图片未加载或加载失败时显示
   - 为图片添加了透明度过渡效果，当 blurhash 显示时图片透明度为 0
   - 为图片添加了 `onLoad` 和 `onError` 事件处理

## 工作原理

1. **初始状态**：当组件首次加载时，所有图片都没有加载状态记录，此时会显示 blurhash
2. **加载过程**：当图片开始加载时，blurhash 继续显示，图片设置为透明
3. **加载成功**：图片加载完成后，更新状态，隐藏 blurhash，显示真实图片
4. **加载失败**：如果图片加载失败，继续显示 blurhash 作为占位符

这样用户就能在图片加载过程中看到漂亮的 blurhash 占位符，提供更好的用户体验，避免空白或破损图片的显示。

---

