提问人:Michael 提问时间:10/25/2023 最后编辑:Michael 更新时间:10/25/2023 访问量:113
VueJs:在加载镜像时显示一个骨架加载器
VueJs: show a skeleton loader when the image is loading
问:
我需要检查图像是否由浏览器加载,以及是否获取了所有数据,然后显示内容,如果没有,请改为显示组件。因此,如果一张图像的加载速度比其他图像快,我会想显示内容,而其余的图像将有一个骨架,直到它们一个接一个地完全加载。<ItemCardSkeleton />
我的尝试:我通过添加 来检查图像是否已加载,因此如果设置为它意味着它已加载。然后我检查来自 API 的数据是否处于待处理状态(我向子组件传递了一个 prop),如果是,则显示 .@load
isLoaded
true
isLoaded
true
<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>
答:
问题是要渲染的必须为 true,以便可以设置为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();
正如你所看到的,只有当已经为 true 时才能设置为 true(代码只运行 10 秒,使用此逻辑永远不会为 trueisLoaded
isLoaded
isLoaded
请尝试以下操作
从@load
<img
用于将图像预加载到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
为了使用上面的类似代码进行演示,需要设置一些其他代码(这是在加载答案时完成的,但在此演示中,只是在 5 秒后的超时中)isLoaded = true
new 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)
评论
code <ItemCardSkeleton v-if="!isLoaded" />
code onMounted(() => { const img = new Image(getSrc('.jpg')); img.onload = () => { isLoaded.value = true; }; });
img.onerror
onMounted
new Image
new Image
如果要传递指示数据是否仍在加载的布尔值,则应更改为 in。此外,初始化为每个新卡组件。:pending="storeRecipes.data"
:pending="storeRecipes.pending"
ItemSwiper.vue
isLoaded
false
项目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')
@load
isLoaded
false
通过这些更改,每张卡将独立控制自己的加载状态,确保骨架加载器显示,直到为每张卡加载数据和图像。
评论
.value"
v-if
isLoaded
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" />
评论
<img
isLoading
pending
getSrc
code <ItemCardSkeleton v-if="pending || !isLoaded" />
isLoaded
v-else
isLoaded