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

Vue:检测图片加载状态,按需显示骨架屏问题求助

问题解决:图片加载完成前骨架屏提前消失

问题背景

需求是:页面需等待数据获取完成且对应卡片图片加载完成后,才显示真实卡片内容;否则显示<ItemCardSkeleton />骨架屏。同时支持部分图片先加载完成时,对应卡片显示内容,其余卡片维持骨架屏直至加载完成。

当前实现的问题:页面刷新时,仅在数据请求pending阶段显示骨架屏,数据请求结束后,哪怕图片还在加载,骨架屏就直接消失,不符合预期。

核心问题分析

  1. 原方案用单个isLoaded布尔值记录所有卡片的加载状态,只要有一个图片加载完成,isLoaded就变为true,父组件直接切换显示所有真实卡片,忽略了其他未加载完成的图片。
  2. 初始时isLoaded为undefined,isLoaded === false的判断不成立,数据请求结束后直接跳过骨架屏逻辑。

解决思路

将单个全局的加载状态,改为每个卡片独立的加载状态,通过记录每个recipe的id对应的加载状态,实现卡片级别的加载控制:

  1. 父组件维护一个对象,存储每个recipe id对应的图片加载状态;
  2. 数据加载完成后,初始化所有卡片的加载状态为false;
  3. 子组件图片加载完成(或失败)时,通知父组件更新对应id的加载状态;
  4. 父组件针对每个卡片,根据其独立的加载状态决定显示骨架屏还是真实内容。

具体代码修改

父组件(ItemSwiper.vue)

<template>
    <Swiper
        class="swiper"
        :breakpoints="swiperOptions.breakpoints"
        :pagination="{
            clickable: true,
        }"
        :loop="true"
        :modules="swiperOptions.modules">
        <!-- 数据请求中:显示批量骨架屏 -->
        <template v-if="storeRecipes.pending">
            <SwiperSlide class="swiper__slide" v-for="item in 3" :key="item">
                <ItemCardSkeleton />
            </SwiperSlide>
        </template>
        <!-- 数据已加载:逐个判断卡片加载状态 -->
        <template v-else>
            <template v-for="recipe in storeRecipes.data" :key="recipe.id">
                <SwiperSlide class="swiper__slide">
                    <!-- 对应卡片未加载完成时显示骨架屏 -->
                    <ItemCardSkeleton v-if="!cardLoadedStates[recipe.id]" />
                    <!-- 加载完成后显示真实卡片 -->
                    <ItemCard 
                        v-else
                        :data="recipe" 
                        @update:is-loaded="(isLoaded) => updateCardLoaded(recipe.id, isLoaded)" 
                    />
                </SwiperSlide>
                <div class="swiper-custom-pagination"></div>
            </template>
        </template>
    </Swiper>
</template>

<script setup>
import { onMounted, ref, watch } from 'vue';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Pagination } from 'swiper/modules';
import 'swiper/css/free-mode';
import 'swiper/css/pagination';
import 'swiper/css';
import { useStoreRecipes } from '@/stores/recipes/storeRecipes.js';

import ItemCard from '@/components/ItemCard.vue';
import ItemCardSkeleton from '@/components/SkeletonLoaders/ItemCardSkeleton.vue';

const storeRecipes = useStoreRecipes();
// 用对象存储每个卡片的加载状态,key为recipe的id
const cardLoadedStates = ref({});

onMounted(() => {
    storeRecipes.loadRecipes();
});

// 监听recipes数据变化,初始化每个卡片的加载状态为false
watch(() => storeRecipes.data, (newData) => {
    if (newData.length) {
        const initialStates = {};
        newData.forEach(recipe => {
            initialStates[recipe.id] = false;
        });
        cardLoadedStates.value = initialStates;
    }
}, { immediate: true });

// 更新对应id卡片的加载状态
const updateCardLoaded = (recipeId, isLoaded) => {
    cardLoadedStates.value[recipeId] = isLoaded;
};
</script>

子组件(ItemCard.vue)

<template>
    <div class="card">
        <div class="card__item">
            <img
                class="card__image"
                @load="handleImageLoad"
                @error="handleImageLoad" <!-- 加载失败也标记为已加载,避免骨架屏一直显示 -->
                :src="getSrc('.jpg')"
                :alt="data.alt"/>
            <div class="card__content">
                <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>
const props = defineProps(['data']);
const emit = defineEmits(['update:is-loaded']);

const getSrc = ext => {
    return new URL(
        `../assets/images/recipe/${props.data.image}${ext}`,
        import.meta.url
    ).href;
};

// 图片加载完成/失败时通知父组件
const handleImageLoad = () => {
    emit('update:is-loaded', true);
};
</script>

效果说明

  • 数据请求阶段:显示3个骨架屏;
  • 数据加载完成后:每个卡片初始显示骨架屏,对应图片加载完成后自动切换为真实卡片;
  • 图片加载失败时:自动切换为真实卡片,避免骨架屏一直显示;
  • 完全符合“部分加载完就显示对应卡片,其余维持骨架屏”的需求。

内容的提问来源于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 19:05:01