Vue3 默认图片无限加载问题
Vue3 默认图片无限加载问题
问题描述
在云图库应用的 Manager Pictures 页面中,某些图片在部署后会无限循环加载。这个问题与默认图片的处理和图片加载错误有关。

分析
1. 图片加载问题
无限加载问题可能是由 handleImageError 函数及其处理图片错误的方式引起的:
▼typescript复制代码const handleImageError = (event: Event) => { const imgElement = event.target as HTMLImageElement; imgElement.src = defaultPlaceholderImage; // 这里可能造成循环 imgElement.classList.add("opacity-50"); };
如果 defaultPlaceholderImage 加载失败,会再次触发错误事件,导致无限循环。为了避免这种情况,可以添加一个检查来防止循环:
▼typescript复制代码const handleImageError = (event: Event) => { const imgElement = event.target as HTMLImageElement; if (imgElement.src !== defaultPlaceholderImage) { imgElement.src = defaultPlaceholderImage; imgElement.classList.add("opacity-50"); } };
2. 缓存机制潜在问题
requestCache使用Map存储数据,但没有大小限制和清理机制,可能导致内存泄漏。- 缓存键使用序列化的
queryParams字符串,可能过于敏感,导致缓存命中率低。
3. 性能优化建议
- 懒加载:对列表中的图片实现懒加载。
- 虚拟滚动:当处理大量数据时,使用虚拟滚动优化渲染性能。
- 计算属性优化:优化计算属性,避免频繁重新计算。
4. 列表渲染优化
▼vue复制代码<tr v-for="item in processedListInfo" :key="item.id" v-memo="[item.id, item.name, item.thumbnailUrl, item.category, item.tags?.length]" >
v-memo 数组包含了许多属性,可能影响性能。建议只保留必要的属性。
5. 防抖/节流优化
LoadList函数使用了 500ms 的节流间隔,可能需要根据实际情况调整。handleKeyPress函数使用了 1000ms 的节流时间,可能影响用户体验。
6. 监听器优化
▼typescript复制代码watch(() => ({ name: PageInfo.value.name, current: PageInfo.value.current, // ... }),
这种写法每次都会创建新对象,可能导致不必要的触发。建议分别监听必要的属性。
7. 全屏功能潜在问题
▼typescript复制代码const toggleFullscreen = (id: string | undefined) => { // ... if (item.url) { imageElement.src = item.url; const { toggle } = useFullscreen(imageElement); toggle(); // ... } };
在切换全屏时直接修改 src 属性可能导致闪烁。建议预加载原图。
8. 内存管理
imageRefs在组件卸载时没有清理。- 事件监听器在组件卸载时没有移除。
解决方案
解决方案步骤
- 修复无限加载问题。
- 优化图片加载和错误处理逻辑。
- 添加图片加载状态指示器。
- 优化缓存机制。
代码修改
▼typescript复制代码// filepath: /MyPictures/snake-fronted/src/views/pictures/ManagerPictures.vue // ...existing code... // 添加新的图片加载状态 const imageLoadingStates = ref<{ [key: string]: boolean }>({}); const failedImages = ref<Set<string>>(new Set()); // 修改图片错误处理函数 const handleImageError = (event: Event, itemId: string) => { const imgElement = event.target as HTMLImageElement; const currentSrc = imgElement.src; if (currentSrc === defaultPlaceholderImage || failedImages.value.has(itemId)) { return; } failedImages.value.add(itemId); imgElement.src = defaultPlaceholderImage; imgElement.classList.add("opacity-50"); imageLoadingStates.value[itemId] = false; }; // 添加图片加载处理函数 const handleImageLoad = (itemId: string) => { imageLoadingStates.value[itemId] = false; }; // 添加图片加载开始处理函数 const handleImageLoadStart = (itemId: string) => { imageLoadingStates.value[itemId] = true; }; // 修改模板以包含图片加载状态指示器 <template> <!-- ...existing code... --> <td class="whitespace-nowrap p-4 text-sm text-gray-500 border-r border-gray-100/50 text-center"> <div class="relative inline-block"> <img v-if="item.url || item.thumbnailUrl" ref="imageRefs" class="inline-block size-14 rounded-md object-cover cursor-pointer hover:opacity-90 transition-opacity shadow-sm hover:shadow-md" :src="failedImages.has(item.id) ? defaultPlaceholderImage : (item.thumbnailUrl ?? item.url)" :alt="item.name || DefaultPictureTexts.UNNAMED_PICTURE" @click="toggleFullscreen(item.id)" @error="(e) => handleImageError(e, item.id)" @load="() => handleImageLoad(item.id)" @loadstart="() => handleImageLoadStart(item.id)" /> <div v-if="imageLoadingStates[item.id]" class="absolute inset-0 flex items中心 justify-center bg-gray-100/50 rounded-md"> <div class="animate-spin rounded-full h-6 w-6 border-2 border-primary border-t-transparent"></div> </div> </div> </td> <!-- ...existing code... --> </template> // 修改 toggleFullscreen 函数以支持图片预加载 const toggleFullscreen = async (id: string | undefined) => { if (!id) return; const item = ListInfo.value.find((picture) => picture.id === id); if (!item?.url) return; try { await new Promise((resolve, reject) => { const img = new Image(); img.src = item.url!; img.onload = resolve; img.onerror = reject; }); const imageElement = imageRefs.value.find( (ref) => ref?.getAttribute("src") === (item.thumbnailUrl ?? item.url) ); if (imageElement) { imageElement.src = item.url; const { toggle } = useFullscreen(imageElement); toggle(); imageElement.addEventListener("fullscreenchange", () => { if (!document.fullscreenElement && item.thumbnailUrl) { imageElement.src = item.thumbnailUrl; } }, { once: true }); } } catch (error) { Message.error('Failed to load full size image'); } }; // 清理函数 onUnmounted(() => { imageRefs.value = []; failedImages.value.clear(); imageLoadingStates.value = {}; });
这些修改解决了无限加载问题,改善了用户体验,并确保了更好的内存管理和错误处理。
结论
通过实施这些优化,可以解决无限加载问题,并提升云图库应用中 Manager Pictures 页面的整体性能和可靠性。
评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
作者分享
花了几天时间 整了个脚手架工具
6
Day01-研0 开学,今天学吴恩达机器学习,搞懂理论。补补基础 7月进组。
6
一个月构造了一个使用 Vue 3、TypeScript 和 Tailwind4 构建的组件库,目前完成了第一阶段的开发,主要是来自云图库项目中自己封装的组件抽取成公共组件,使用tsx进行开发,比.vue文件更加的灵活可控。后续将持续更新,完善组件库,支持React版本。项目地址 https://github.com/Zhengke0110/MyComponents; 预览地址:https://lib.timu.fun
特性
🚀 基于 Vue 3 和 TypeScript 构建
🎨 使用 Tailwind CSS 实现灵活的样式定制
📦 支持按需引入
🌙 内置暗色模式支持
⌨️ 完整的键盘导航支持
📱 响应式设计
🎯 完整的 TypeScript 类型支持
8
完结:云图库,预览地址:http://snake.timu.fun,页面大部分时间花在了前端样式上,没有使用组件库,全程使用Unocss+icon 进行编写,封装了一套不太合理的组件库(.vue文件)准备重构组件库,用tsx实现(开新坑),后续将单独抽取成一个项目;后端在接入so-token处发生了点意外(管理员登录后的权限列表始终为空,未找到问题所在。。),没有采用RBAC的形式实现,还是按照老方法在SpaceUser表添加了一个团队角色,通过判断是否为本项目成员进行鉴权(实现简单),前端没有实现到团队共同编辑那步,下一步准备自己先实现一个canvas图片编辑的功能,再回来继续实现,已经部署;云图库项目地址:https://github.com/Zhengke0110/MyPictures 求Start
17
新年打个卡,云图库,前端封装通用的lib 后续将做一个自己的组件库的展示
3
