feat(template-card): add media placeholder and smooth video fade-in

add gradient background media placeholder for empty states
update video to use 0.18s ease opacity fade-in transition
add videoReady state to track when video is ready to display
adjust z-indexes for correct media layer stacking
fix video rendering condition and add state reset watchers
update template media container background color
This commit is contained in:
duanshuwen
2026-07-23 22:10:24 +08:00
parent 3cc95e7f51
commit 2f9ede3993
2 changed files with 48 additions and 2 deletions

View File

@@ -4,10 +4,12 @@
:class="{ 'is-active': active }"
>
<view class="template-media">
<view class="template-media-placeholder" />
<image v-if="templatePosterUrl" class="template-image" :src="templatePosterUrl" mode="aspectFill" />
<video
v-else-if="templateVideoUrl && playing && !videoLoadFailed"
v-if="templateVideoUrl && playing && !videoLoadFailed"
class="template-video"
:class="{ 'is-ready': videoReady }"
:src="templateVideoUrl"
autoplay
muted
@@ -18,6 +20,8 @@
:show-fullscreen-btn="false"
:enable-progress-gesture="false"
object-fit="cover"
@play="handleVideoReady"
@timeupdate="handleVideoReady"
@error="handleVideoError"
/>
<view class="template-media-shade" />
@@ -57,6 +61,7 @@ const emit = defineEmits(["select"]);
const VIDEO_EXT_REGEXP = /\.(mp4|mov|webm|m4v)(?:[?#].*)?$/i;
const videoLoadFailed = ref(false);
const videoReady = ref(false);
const templateMediaUrl = computed(() => {
return props.template.templateContentUrl || props.template.coverPhotoUrl || "";
@@ -75,13 +80,26 @@ const templatePosterUrl = computed(() => {
return templateMediaUrl.value;
});
const handleVideoReady = () => {
videoReady.value = true;
};
const handleVideoError = () => {
videoReady.value = false;
videoLoadFailed.value = true;
};
watch(templateVideoUrl, () => {
videoReady.value = false;
videoLoadFailed.value = false;
});
watch(() => props.playing, (playing) => {
videoReady.value = false;
if (playing) {
videoLoadFailed.value = false;
}
});
</script>
<style scoped lang="scss">

View File

@@ -42,18 +42,45 @@
flex: 0 0 var(--template-card-media-height, 358px);
height: var(--template-card-media-height, 358px);
overflow: hidden;
background: #0f7069;
background: #0a4d46;
}
.template-media-placeholder,
.template-image,
.template-video {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
}
.template-media-placeholder {
background:
radial-gradient(
90% 48% at 50% 30%,
rgba(255, 255, 255, 0.12),
rgba(255, 255, 255, 0) 70%
),
linear-gradient(160deg, #17383a 0%, #0f7069 58%, #0a4d46 100%);
}
.template-image {
z-index: 1;
}
.template-video {
z-index: 2;
opacity: 0;
pointer-events: none;
transition: opacity 0.18s ease;
}
.template-video.is-ready {
opacity: 1;
}
.template-media-shade {
@@ -62,6 +89,7 @@
right: 0;
bottom: 0;
left: 0;
z-index: 3;
background:
radial-gradient(
92% 45% at 50% 28%,