VueJs:在图像加载时显示 backbone 加载器

sg2wtvxw  于 2023-11-21  发布在  Vue.js
关注(0)|答案(2)|浏览(262)

我需要检查浏览器是否加载了图像,是否获取了所有数据,然后显示内容,如果没有,则显示<ItemCardSkeleton />组件。因此,如果一个图像加载速度比其他图像快,我会想要显示内容,其余图像将有一个 backbone ,直到它们一个接一个地完全加载。
我的尝试:我通过添加@load来检查图像是否被加载,因此如果isLoaded被设置为true,则意味着它被加载。然后我检查来自API的数据是否处于挂起状态(我向子组件传递了一个prop),如果isLoadedtrue,则显示<ItemCardSkeleton />
问题:当我把节流设置为2G并重新加载页面时,所有的加载器在几秒钟内同时出现,<ItemCardSkeleton />无限加载,所以我从来没有看到真实的卡出现。此外,我可以看到模板中的{{ isLoaded }}总是false
父组件ItemSwiper.vue:

<template>
    <Swiper>
        <template v-for="recipe in storeRecipes.data" :key="recipe.id">
            <SwiperSlide class="swiper__slide">
                <ItemCard :data="recipe" :pending="storeRecipes.data" />
            </SwiperSlide>
            <div class="swiper-custom-pagination"></div>
        </template>
    </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>

字符串
子组件ItemCard.vue:

<template>
    <div class="card">
        <div class="card__item">
            <ItemCardSkeleton v-if="pending || !isLoaded" />
            <template v-else>
                <img
                    class="card__image"
                    @load="isLoaded = true"
                    :src="getSrc('.jpg')"
                    :alt="data.alt"
                    width="15.625rem" />
                <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>
            </template>
        </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;
};
</script>

v9tzhpje

v9tzhpje1#

问题是isLoaded必须为真才能呈现<img,这样isLoaded才能设置为true
所以,很明显,你有一个不可能的情况,因为isLoaded不能被设置为真,直到它已经是真的。
为了说明你的代码在做什么,考虑一下:

let isLoaded = false;
let pending = true;
function x(n=0) {
    console.log(JSON.stringify({n, pending, isLoaded}));
    if (n > 9) return;
    if (pending || !isLoaded) {
        setTimeout(() => {
            pending = false;
            x(n+1);
        }, 1000);
    } else {
        isLoaded = true;
    }
}
x();

字符串
正如您所看到的,只有当isLoaded已经为真时,isLoaded才能被设置为真(代码只运行10秒,isLoaded在此逻辑下永远不会为真
尝试以下
<img中删除@load
使用onMounted * 预加载图像到new Image
当图像加载时,设置isLoaded = true
比如说:

<template>
    <div class="card">
        <div class="card__item">
            <ItemCardSkeleton v-if="pending || !isLoaded" />
            <template v-else>
                <img
                    class="card__image"
                    :src="getSrc('.jpg')"
                    :alt="data.alt"
                    width="15.625rem" />
                <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>
            </template>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted } 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;
};
onMounted(() => {
    const img = new Image;
    img.onload = () => isLoaded.value = true;
    img.src = getSrc('.jpg');
});
</script>


唯一的问题是,我看不出pending是如何变成false的,但我想这取决于您
为了演示上面的类似代码,其他一些代码需要设置isLoaded = true(这是在new Image加载到答案中时完成的,但在这个演示中,只是在5秒后超时)

let isLoaded = false;
let pending = true;
function x(n=0) {
    console.log(JSON.stringify({n, pending, isLoaded}));
    if (n > 9) return;
    if (pending || !isLoaded) {
        setTimeout(() => {
            pending = false;
            x(n+1);
        }, 1000);
    } else {
        isLoaded = true;
    }
}
x();
setTimeout(() => {
    isLoaded = true;
}, 5000)

bttbmeg0

bttbmeg02#

如果你想传递一个布尔值来表示数据是否还在加载,你应该在ItemSwiper.vue中将:pending="storeRecipes.data"改为:pending="storeRecipes.pending"。另外,对于每个新的卡组件,将isLoaded初始化为false

ItemSwiper.vue

<template>
    <Swiper>
        <template v-for="recipe in storeRecipes.data" :key="recipe.id">
            <SwiperSlide class="swiper__slide">
                <!-- Pass the boolean flag indicating loading state -->
                <ItemCard :data="recipe" :pending="storeRecipes.pending" />
            </SwiperSlide>
        </template>
    </Swiper>
</template>

字符串

ItemCard.vue

<template>
  <div class="card">
    <div class="card__item">
      <ItemCardSkeleton v-if="pending || !isLoaded" />
      <template v-else>
        <img
          class="card__image"
          @load="isLoaded = true" <!-- This should set isLoaded to true when image loads -->
          :src="getSrc('.jpg')"
          :alt="data.alt"
          width="15.625rem" />
        <!-- ... -->
      </template>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ItemCardSkeleton from '@/components/SkeletonLoaders/ItemCardSkeleton.vue';

const props = defineProps(['data', 'pending']);

// Initialize as false for each new component
const isLoaded = ref(false);

// ...
</script>


请确保getSrc('.jpg')提供的路径正确。如果路径不正确或镜像加载有任何问题,则不会触发@load事件,isLoaded将保持false
通过这些更改,每张卡将独立控制其自己的加载状态,确保 backbone 加载器显示,直到为每张卡加载数据和图像。

相关问题