Vue.js图片加载时骨架屏无限加载问题求助(isLoaded始终为false)
Vue3 骨架屏无限加载问题修复方案
问题场景
需求是等待图片加载完成且API数据获取后,才显示真实卡片,否则展示ItemCardSkeleton组件;要求加载完成的卡片逐个显示。但在2G网络节流测试时,所有骨架屏无限加载,isLoaded始终为false,无法显示真实卡片。
核心错误原因
- pending状态传递错误:父组件把
storeRecipes.data传给子组件的pending属性,而data是数组类型,只要API返回数据就会是真值,导致子组件的pending一直为true,骨架屏永远不会隐藏。 - 图片渲染逻辑死循环:子组件仅在
pending为false且isLoaded为true时才渲染img标签,但isLoaded依赖img的@load事件触发——骨架屏显示时img不渲染→@load永远无法触发→isLoaded一直为false→骨架屏持续显示,形成闭环。
修复步骤及代码
1. 修正Store的加载状态标记
在storeRecipes.js中添加明确的加载状态,替代用data判断请求状态的错误逻辑:
// @/stores/recipes/storeRecipes.js import { defineStore } from 'pinia'; export const useStoreRecipes = defineStore('recipes', { state: () => ({ data: [], isLoading: false // 新增:标记API请求状态 }), actions: { async loadRecipes() { this.isLoading = true; try { // 替换为实际API请求逻辑 const response = await fetch('你的API地址'); this.data = await response.json(); } catch (error) { console.error('菜谱数据加载失败:', error); } finally { this.isLoading = false; } } } });
2. 修改父组件的pending属性传递
父组件传递Store的isLoading状态给子组件,而非data:
<!-- ItemSwiper.vue --> <template> <Swiper> <template v-for="recipe in storeRecipes.data" :key="recipe.id"> <SwiperSlide class="swiper__slide"> <!-- 传递正确的加载状态 --> <ItemCard :data="recipe" :pending="storeRecipes.isLoading" /> </SwiperSlide> </template> <div class="swiper-custom-pagination"></div> </Swiper> </template> <script setup> import { onMounted } from 'vue'; import { Swiper, SwiperSlide } from 'swiper/vue'; import { useStoreRecipes } from '@/stores/recipes/storeRecipes.js'; import ItemCard from '@/components/ItemCard.vue'; const storeRecipes = useStoreRecipes(); onMounted(() => { storeRecipes.loadRecipes(); }); </script>
3. 重构子组件的渲染逻辑
始终渲染img标签(保证@load事件能触发),通过CSS控制显示/隐藏,同时添加图片加载错误处理:
<!-- ItemCard.vue --> <template> <div class="card"> <div class="card__item"> <!-- 骨架屏仅在请求中或图片未加载完成时显示 --> <ItemCardSkeleton v-if="pending || !isLoaded" class="skeleton" /> <!-- 始终渲染图片,通过CSS控制可见性 --> <img class="card__image" :class="{ 'card__image--loaded': isLoaded }" @load="isLoaded = true" @error="handleImageError" :src="getSrc('.jpg')" :alt="data.alt" width="15.625rem" /> <!-- 内容在API请求完成且图片加载后显示 --> <div class="card__content" v-if="!pending && isLoaded"> <h2 class="card__title">{{ data.title }}</h2> <p class="card__text">{{ data.text }}</p> <router-link class="card__link" :to="{ name: 'Home' }"> View more </router-link> </div> </div> </div> </template> <script setup> import { ref } from 'vue'; import ItemCardSkeleton from '@/components/SkeletonLoaders/ItemCardSkeleton.vue'; const props = defineProps(['data', 'pending']); const isLoaded = ref(false); const getSrc = ext => { return new URL( `../assets/images/recipe/${props.data.image}${ext}`, import.meta.url ).href; }; // 处理图片加载失败,避免骨架屏无限显示 const handleImageError = () => { isLoaded.value = true; console.error(`图片加载失败: ${props.data.image}`); }; </script> <style scoped> .card__item { position: relative; } .card__image { visibility: hidden; opacity: 0; transition: opacity 0.3s ease; } .card__image--loaded { visibility: visible; opacity: 1; } .skeleton { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } </style>
修复效果说明
- API请求期间,所有卡片显示骨架屏;请求完成后,图片开始加载,加载完成的卡片会逐个替换掉骨架屏。
- 图片加载失败时,会自动跳过骨架屏,显示卡片内容(可根据需求调整错误处理逻辑)。
- 2G网络下能正常触发图片加载事件,不会出现无限加载的情况。
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

