英文:
How can I prevent the temporary loading of a placeholder image in this Vue 3 app?
问题
I have been working on an SPA with Vue 3, TypeScript and The Movie Database (TMDB) API.
我一直在使用 Vue 3、TypeScript 和 The Movie Database (TMDB) API 开发单页面应用 (SPA)。
I want to display the movie poster on the movie details page if there is a poster, otherwise I want to display a placeholder (generic-poster-big.png).
如果有电影海报,我希望在电影详情页面上显示电影海报,否则我希望显示一个占位符 (generic-poster-big.png)。
For this purpose, in src\components\MovieDetails.vue
, I have:
为了实现这个目的,在 src\components\MovieDetails.vue
文件中,我有以下代码:
<template>
<div class="poster-container text-center text-sm-start my-3">
<img
:src="genericPoster"
:alt="movie?.title"
class="img-fluid shadow-sm"
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useRoute } from "vue-router";
import axios from "axios";
import env from "../env";
import { Movie } from "../models/Movie";
export default defineComponent({
name: "MovieDetails",
data() {
return {
route: useRoute(),
genericPosterBig: require("../assets/generic-poster-big.png"),
movie: null as Movie | null
};
},
mounted() {
this.getMovieDetails();
},
methods: {
getMovieDetails() {
axios
.get(
`${env.api_url}/movie/${this.route.params.id}?api_key=${env.api_key}`
)
.then((response) => {
this.movie = response.data;
})
.catch((err) => console.log(err));
},
},
computed: {
genericPoster() {
return !this.movie?.poster_path
? this.genericPosterBig
: `https://image.tmdb.org/t/p/w500/${this.movie?.poster_path}`;
},
},
});
</script>
The problem
问题是:
When there is a movie poster, before it loads, the generic poster appears, for a fraction of a second, yet visible.
当有电影海报时,在电影海报加载之前,通常会短暂地显示占位符,尽管它是可见的。
Questions
问题是:
-
What am I doing wrong?
我做错了什么?
-
What is the most reliable way to fix this problem?
什么是最可靠的方法来解决这个问题?
英文:
I have been working on an SPA with Vue 3, TypeScript and The Movie Database (TMDB) API.
I want to display the movie poster on the movie details page if there is a poster, otherwise I want to display a placeholder (generic-poster-big.png).
For this purpose, in src\components\MovieDetails.vue
, I have:
<template>
<div class="poster-container text-center text-sm-start my-3">
<img
:src="genericPoster"
:alt="movie?.title"
class="img-fluid shadow-sm"
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useRoute } from "vue-router";
import axios from "axios";
import env from "../env";
import { Movie } from "../models/Movie";
export default defineComponent({
name: "MovieDetails",
data() {
return {
route: useRoute(),
genericPosterBig: require("../assets/generic-poster-big.png"),
movie: null as Movie | null
};
},
mounted() {
this.getMovieDetails();
},
methods: {
getMovieDetails() {
axios
.get(
`${env.api_url}/movie/${this.route.params.id}?api_key=${env.api_key}`
)
.then((response) => {
this.movie = response.data;
})
.catch((err) => console.log(err));
},
computed: {
genericPoster() {
return !this.movie?.poster_path
? this.genericPosterBig
: `https://image.tmdb.org/t/p/w500/${this.movie?.poster_path}`;
},
}
});
</script>
The problem
When there is a movie poster, before it loads, the generic poster appears, for a fraction of a second, yet visible.
Questions
- What am I doing wrong?
- What is the most reliable way to fix this problem?
答案1
得分: 4
以下是代码的翻译部分:
类似以下内容应该能解决问题,已标记的部分为新增内容
<template>
<div class="poster-container text-center text-sm-start my-3">
<!-- ************ -->
<img v-if="loaded"
:src="genericPoster"
:alt="movie?.title"
class="img-fluid shadow-sm"
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useRoute } from "vue-router";
import axios from "axios";
import env from "../env";
import { Movie } from "../models/Movie";
export default defineComponent({
name: "MovieDetails",
data() {
return {
//************************
loaded: false,
route: useRoute(),
genericPosterBig: require("../assets/generic-poster-big.png"),
movie: null as Movie | null
};
},
mounted() {
this.getMovieDetails();
},
methods: {
getMovieDetails() {
axios
.get(
`${env.api_url}/movie/${this.route.params.id}?api_key=${env.api_key}`
)
.then((response) => {
this.movie = response.data;
})
.catch((err) => console.log(err))
//****************
.finally(() => this.loaded = true);
},
computed: {
genericPoster() {
return !this.movie?.poster_path
? this.genericPosterBig
: `https://image.tmdb.org/t/p/w500/${this.movie?.poster_path}`;
},
}
});
</script>
希望这个翻译对您有所帮助。如果您需要进一步的信息或翻译,请随时告诉我。
英文:
Something like the following should do the trick, added lines marked with **********
<template>
<div class="poster-container text-center text-sm-start my-3">
<!-- ************ -->
<img v-if="loaded"
:src="genericPoster"
:alt="movie?.title"
class="img-fluid shadow-sm"
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useRoute } from "vue-router";
import axios from "axios";
import env from "../env";
import { Movie } from "../models/Movie";
export default defineComponent({
name: "MovieDetails",
data() {
return {
//************************
loaded: false,
route: useRoute(),
genericPosterBig: require("../assets/generic-poster-big.png"),
movie: null as Movie | null
};
},
mounted() {
this.getMovieDetails();
},
methods: {
getMovieDetails() {
axios
.get(
`${env.api_url}/movie/${this.route.params.id}?api_key=${env.api_key}`
)
.then((response) => {
this.movie = response.data;
})
.catch((err) => console.log(err))
//****************
.finally(() => this.loaded = true);
},
computed: {
genericPoster() {
return !this.movie?.poster_path
? this.genericPosterBig
: `https://image.tmdb.org/t/p/w500/${this.movie?.poster_path}`;
},
}
});
</script>
答案2
得分: 1
以下是翻译好的部分:
这种行为是因为你的解决方案而预期的,但我理解你希望为了用户体验而改进这一点。
问题在于在数据可用之前组件已挂载,所以你需要设置一个逻辑来处理加载过程中的情况。
我看到有两种解决方案:
- 仅在主要图像加载失败时(即URL错误或为空时)设置占位图像。因此,只有在尝试加载主要图像后才会加载占位图像,而不是在之前。
在加载过程中,使用图像的加载旋转器,而不是图像占位符(图像占位符应该在获取海报详情后才出现)。这有助于避免布局闪烁:它将保留所需的图像空间。
<template>
<div class="poster-container text-center text-sm-start my-3">
<div v-if="loading">加载中...</div>
<img
v-else
:src="movie.poster_path"
:alt="movie.title"
class="img-fluid shadow-sm"
@error="setPlaceholder"
/>
</div>
</template>
<script>
{
data() {
return {
movie: null,
loading: true,
genericPosterBig: null,
}
}
mounted() {
this.getMovieDetails()
},
methods: {
getMovieDetails() {
this.loading = true
axios.get(`${env.api_url}/movie/${this.route.params.id}?api_key=${env.api_key}`)
.then(({ data }) => {
this.movie = data;
})
.catch((err) => console.log(err))
.finally(() => {
this.loading = false
});
},
setPlaceholder(event) {
// 仅在尚未分配时加载图像
this.genericPosterBig ||= require("../assets/generic-poster-big.png")
event.target.src = this.genericPosterBig
}
}
}
</script>
- 使用Nuxt框架,你可以在挂载电影详情页面之前预加载电影详情,使用
useAsyncData()
可组合(或选项API),这样你就可以确保在创建详情页面时已经拥有了所有电影数据(因此不会出现闪烁)。
{
// 这将阻止页面渲染,直到承诺解决为止。
async asyncData() {
const movieDetails = await axios
.get(`${env.api_url}/movie/${this.route.params.id}?api_key=${env.api_key}`)
.then((response) => response.data)
return { movieDetails }
}
}
如果有任何其他翻译需求,请告诉我。
英文:
This behaviour is expected given your solution, but I understand you want to improve this for UX reasons.
The problem is that your component is mounted before the data is available, so you need to set a logic on what to do while it's loading
I see two solutions for this:
- Set your placeholder image only in case the main image fails to load (i.e. when the url is wrong or it's empty). So it will only loads your placeholder after trying to load the main image, not before.
While it loads, use a loading spinner on your image while it's loading, not the image placeholder (which should appear only once the poster details has been fetched). This is useful to avoid layout flickering: it will reserve the space required by the image.
<template>
<div class="poster-container text-center text-sm-start my-3">
<div v-if="loading">Loading spinner...</div>
<img
v-else
:src="movie.poster_path"
:alt="movie.title"
class="img-fluid shadow-sm"
@error="setPlaceholder"
/>
</div>
</template>
<script>
{
data() {
return {
movie: null,
loading: true,
genericPosterBig: null,
}
}
mounted() {
this.getMovieDetails()
},
methods: {
getMovieDetails() {
this.loading = true
axios.get(`${env.api_url}/movie/${this.route.params.id}?api_key=${env.api_key}`)
.then(({ data }) => {
this.movie = data;
})
.catch((err) => console.log(err))
.finally(() => {
this.loading = false
});
},
setPlaceholder(event) {
// This loads the image only if it's not already assigned
this.genericPosterBig ||= require("../assets/generic-poster-big.png")
event.target.src = this.genericPosterBig
}
}
}
</script>
- Using the Nuxt framework, you could preload the movies details before mounting the movie details page using the
useAsyncData()
composable (or for option api), so you are sure you already have all the movies data when creating the details page (so no flickering).
{
// This will block the page rendering until the promise is settled.
async asyncData() {
const movieDetails = await axios
.get(`${env.api_url}/movie/${this.route.params.id}?api_key=${env.api_key}`)
.then((response) => response.data)
return { movieDetails }
}
}
答案3
得分: 1
以下是翻译好的部分:
显然,在尝试渲染海报之前,确保电影已加载,使用 v-if
解决了该问题:
<img v-if="movie"
:src="genericPoster"
:alt="movie?.title"
class="img-fluid shadow-sm"
/>
英文:
Apparently, making sure that the movie is loaded before trying to render the poster at all, with v-if
, fixed the issue:
<img v-if="movie"
:src="genericPoster"
:alt="movie?.title"
class="img-fluid shadow-sm"
/>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论