VueJs:在加载镜像时显示一个骨架加载器

VueJs: show a skeleton loader when the image is loading

提问人:Michael 提问时间:10/25/2023 最后编辑:Michael 更新时间:10/25/2023 访问量:113

问:

我需要检查图像是否由浏览器加载,以及是否获取了所有数据,然后显示内容,如果没有,请改为显示组件。因此,如果一张图像的加载速度比其他图像快,我会想显示内容,而其余的图像将有一个骨架,直到它们一个接一个地完全加载。<ItemCardSkeleton />

我的尝试:我通过添加 来检查图像是否已加载,因此如果设置为它意味着它已加载。然后我检查来自 API 的数据是否处于待处理状态(我向子组件传递了一个 prop),如果是,则显示 .@loadisLoadedtrueisLoadedtrue<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>
javascript vue.js if-statement vuejs3

评论

1赞 Jaromanda X 10/25/2023
因为 在 true 和 false 之前甚至不会添加到 DOM 中,因此永远不会被调用,因此图像永远无法加载<imgisLoadingpendinggetSrc
0赞 Michael 10/25/2023
pending 在此处设置为 true ,因为它正在等待数据被获取。code <ItemCardSkeleton v-if="pending || !isLoaded" />
0赞 Jaromanda X 10/25/2023
当然,但是虽然挂起是真的,但图像当然不能开始加载......那么,图像将如何开始加载呢?
0赞 Michael 10/25/2023
这就是我在 ItemCardSkeleton 上使用 v-if 的原因。基本上,当数据挂起且 isLoaded 为 false 时,呈现 ItemCardSkeleton。否则,加载 img+card 内容。
0赞 Jaromanda X 10/25/2023
但也必须为 true 才能呈现 - 如何设置为 true?isLoadedv-elseisLoaded

答:

3赞 Jaromanda X 10/25/2023 #1

问题是要渲染的必须为 true,以便可以设置为isLoaded<imgisLoadedtrue

所以,很明显,你有一个不可能的情况,因为在它已经为真之前不能设置为真。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();

正如你所看到的,只有当已经为 true 时才能设置为 true(代码只运行 10 秒,使用此逻辑永远不会为 trueisLoadedisLoadedisLoaded

请尝试以下操作

@load<img

用于将图像预加载到onMountednew 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>

唯一的问题是,我看不出如何变成,但我想这取决于你pendingfalse

为了使用上面的类似代码进行演示,需要设置一些其他代码(这是在加载答案时完成的,但在此演示中,只是在 5 秒后的超时中)isLoaded = truenew Image

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)

评论

0赞 Michael 10/25/2023
谢谢你的澄清!现在,我将删除“待处理”并专注于 isLoaded。所以我添加了 但是,如果我现在渲染 {{ isLoading }},我可以看到值始终是 'false'。此外,骷髅是无限加载的。code <ItemCardSkeleton v-if="!isLoaded" />code onMounted(() => { const img = new Image(getSrc('.jpg')); img.onload = () => { isLoaded.value = true; }; });
0赞 Jaromanda X 10/25/2023
尝试添加一个 进行调试 - 或检查开发人员工具以查看加载映像时是否出错。我认为你记得从vue添加到导入中吗?我没有把它放在答案中,因为我忘记了img.onerroronMounted
0赞 Michael 10/25/2023
我导入了它。控制台不显示错误。
0赞 Jaromanda X 10/25/2023
好的,我在查看更新的代码时犯了一个错误 - 我认为将 src 作为论据......它不会:pnew Imagenew Image
1赞 Jaromanda X 10/25/2023
我可能会做同样的事情,在一切都准备好渲染之前不要渲染
1赞 suchislife 10/25/2023 #2

如果要传递指示数据是否仍在加载的布尔值,则应更改为 in。此外,初始化为每个新卡组件。:pending="storeRecipes.data":pending="storeRecipes.pending"ItemSwiper.vueisLoadedfalse

项目Swiper.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 (项目卡.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')@loadisLoadedfalse

通过这些更改,每张卡将独立控制自己的加载状态,确保骨架加载器显示,直到为每张卡加载数据和图像。

评论

0赞 Michael 10/25/2023
你是对的!我放置了数据而不是意外挂起。谢谢。
0赞 Jaromanda X 10/25/2023
a) 你不在 a 中使用 b) 怎么会变成真?.value"v-ifisLoaded
0赞 suchislife 10/25/2023
不错的收获。更新。
0赞 Michael 10/25/2023
在我定义“待定”的全局存储中,我有一个操作: 因此,当获取数据时,它应该将待处理设置为 false。但是,如果我设置为检查,我看不到骨架组件。code async loadRecipes() { try { this.pending = true; const res = await fetch('/api/recipe'); if (res.ok) { const data = await res.json(); this.data = data; } else { console.error('Error: ', res.status, res.statusText); } } catch (err) { console.error(err); } finally { this.pending = false; } }, code <ItemCardSkeleton v-if="pending" />
0赞 suchislife 10/25/2023
全局存储中挂起状态的反应性可能存在问题。确保 pending 是反应状态,并且您在父组件中正确导入和使用它。