You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 21:14:59