qs_xinchun2026_h5/components/WangfujingScene.vue

505 lines
9.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, computed, watch } from 'vue'
import FuClickArea from './FuClickArea.vue'
import ImagePreloader from '@/utils/preload';
// 组件属性
const props = defineProps({
// 是否为活动状态
active: {
type: Boolean,
default: false
},
// 滚动位置,用于实现视差效果
scrollPosition: {
type: Number,
default: 0
}
})
// 组件事件
const emit = defineEmits(['collect-seal'])
// 福字点击区域状态
const fuClickAreaVisible = ref(true)
const sq3ImageVisible = ref(false)
// 图片加载状态
const imagesLoaded = ref(false)
const isPreloading = ref(false)
// 计算视差效果的偏移量
const parallaxOffset = computed(() => {
// 滚动位置的1/10作为视差偏移
return props.scrollPosition * 0.1
})
// 预加载图片函数
const preloadImages = async () => {
if (imagesLoaded.value || isPreloading.value) return
isPreloading.value = true
try {
// 提取所有图片地址
const imageUrls = images.map(img => img.src)
// 使用ImagePreloader批量预加载图片
await ImagePreloader.preloadAll(imageUrls)
imagesLoaded.value = true
} catch (error) {
console.error('图片预加载失败:', error)
} finally {
isPreloading.value = false
}
}
// 监听激活状态变化,当组件激活时预加载图片
watch(() => props.active, async (newActive) => {
if (newActive) {
await preloadImages()
}
})
// 点击福字区域
const handleFuClick = () => {
fuClickAreaVisible.value = false
sq3ImageVisible.value = true
emit('collect-seal')
}
// 生成带时间戳的图片URL避免缓存
const generateImageUrl = (name) => {
const url = new URL(`/static/wfj/${name}.jpg`, import.meta.url)
// 添加时间戳参数避免浏览器缓存
return url.href
}
// 图片浏览数据
const images = [
{ src: generateImageUrl('img1'), title: '' },
{ src: generateImageUrl('img2'), title: '' },
{ src: generateImageUrl('img3'), title: '' },
{ src: generateImageUrl('img4'), title: '' }
]
const currentImageIndex = ref(0)
// 切换图片
const prevImage = () => {
currentImageIndex.value = (currentImageIndex.value - 1 + images.length) % images.length
}
const nextImage = () => {
currentImageIndex.value = (currentImageIndex.value + 1) % images.length
}
// 页面挂载时的初始化
onMounted(() => {
// 添加动画类,触发入场动画
const container = document.querySelector('.wangfujing-scene-container')
if (container) {
container.classList.add('animate-in')
}
// 如果组件已经激活,立即预加载图片
if (props.active) {
preloadImages()
}
})
</script>
<template>
<section class="wangfujing-scene-container" :class="{ 'active': active }">
<!-- 背景图片层 -->
<div class="background-layer" :style="{ transform: `translateY(${parallaxOffset}px)` }">
<!-- 使用王府井商圈背景图片 -->
<img src="/static/bg/bg3.jpg" alt="王府井商圈" class="background-image" />
</div>
<!-- 福字点击区域 -->
<FuClickArea
:visible="fuClickAreaVisible"
:x-range="630"
:y-range="900"
:y-start="150"
:fu-width="100"
:fu-height="100"
@click="handleFuClick"
/>
<!-- sq3图片 -->
<img
v-if="sq3ImageVisible"
src="/static/images/sq3.png"
alt="新春祝福"
class="sq3-image"
/>
<!-- 图片浏览组件 -->
<div class="image-gallery">
<div class="gallery-image-wrapper">
<div class="loading-overlay" v-if="isPreloading">
<div class="loading-spinner"></div>
</div>
<div class="nav-btn prev-btn" @click="prevImage">
<img src="/static/images/btn_prev.png" alt="上一张" class="nav-icon" />
</div>
<img :src="images[currentImageIndex].src" :alt="images[currentImageIndex].title" mode="widthFit" class="gallery-image" />
<div class="nav-btn next-btn" @click="nextImage">
<img src="/static/images/btn_next.png" alt="下一张" class="nav-icon" />
</div>
</div>
</div>
</section>
</template>
<style scoped>
.wangfujing-scene-container {
position: relative;
width: 100%;
height: auto; /* 高度由内容决定 */
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
background-color: #ff6b35;
}
/* 背景图片层 */
.background-layer {
position: relative;
width: 100%;
transition: transform 0.1s ease;
/* 背景图片决定容器高度 */
height: auto;
}
.background-image {
width: 100%;
height: auto;
display: block;
/* 确保图片完整显示,决定容器高度 */
object-fit: contain;
}
/* 增强动效层 */
.enhancement-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
/* 灯笼增强动效 */
.lanterns {
position: absolute;
top: 15%;
width: 100%;
display: flex;
justify-content: space-between;
padding: 0 30px;
box-sizing: border-box;
}
.lantern {
font-size: 2.5rem;
animation: swing 3s infinite ease-in-out;
opacity: 0.9;
filter: drop-shadow(0 0 15px rgba(255, 215, 0, 0.8));
color: #ffd700;
}
.left-lantern {
animation-delay: 0s;
}
.right-lantern {
animation-delay: 1.5s;
}
@keyframes swing {
0%, 100% { transform: rotate(-10deg); }
50% { transform: rotate(10deg); }
}
/* 福字增强动效 */
.fu-word {
position: absolute;
top: 30%;
left: 65%;
transform: translateX(-50%) rotate(15deg);
font-size: 2rem;
color: #ffd700;
text-shadow: 2px 2px 10px rgba(255, 215, 0, 0.9);
animation: float 4s infinite ease-in-out;
}
@keyframes float {
0%, 100% { transform: translateX(-50%) rotate(15deg) translateY(0); }
50% { transform: translateX(-50%) rotate(15deg) translateY(-15px); }
}
/* 点击指示器 */
.click-indicator {
position: absolute;
top: 55%;
left: 75%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
pointer-events: none;
}
.pulse-circle {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: rgba(255, 215, 0, 0.3);
border: 2px solid rgba(255, 215, 0, 0.6);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(0.8);
opacity: 0.8;
}
100% {
transform: scale(2);
opacity: 0;
}
}
.click-indicator.animate-pulse {
display: block;
}
/* 交互区域 */
.interaction-area {
position: absolute;
top: 55%;
right: 15%;
width: 120px;
height: 100px;
cursor: pointer;
z-index: 20;
}
/* 响应式调整交互区域位置 */
@media (max-width: 640px) {
.interaction-area {
top: 52%;
right: 10%;
width: 100px;
height: 80px;
}
}
/* 烟花效果 */
.fireworks {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 20;
}
.firework {
position: absolute;
font-size: 2rem;
opacity: 0;
animation: firework 3s infinite;
}
.firework-1 {
top: 10%;
left: 20%;
animation-delay: 0s;
}
.firework-2 {
top: 15%;
right: 25%;
animation-delay: 1s;
}
.firework-3 {
top: 8%;
right: 15%;
animation-delay: 2s;
}
.firework-4 {
top: 12%;
left: 25%;
animation-delay: 3s;
}
@keyframes firework {
0%, 100% { opacity: 0; transform: scale(0); }
50% { opacity: 1; transform: scale(1.5); }
}
/* 福印收集标记 */
.seal-collected-mark {
position: absolute;
top: 20px;
right: 20px;
background-color: rgba(255, 107, 53, 0.9);
color: #fff;
padding: 10px 15px;
border-radius: 20px;
font-size: 14px;
display: flex;
align-items: center;
gap: 5px;
animation: fadeIn 0.5s ease;
z-index: 30;
}
.seal-icon {
font-size: 20px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
/* 入场动画 */
.wangfujing-scene-container.animate-in {
animation: sceneFadeIn 1s ease-out;
}
@keyframes sceneFadeIn {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
/* sq3图片 */
.sq3-image {
position: absolute;
top: 220rpx;
right: -6rpx;
width: auto;
height: auto;
max-width: 300rpx;
z-index: 20;
animation: fadeIn 0.5s ease;
}
/* 图片浏览组件 */
.image-gallery {
position: absolute;
left: 50%;
top: 1400rpx;
transform: translateX(-50%);
width: 653rpx;
height: 453rpx;
background-image: url('/static/wfj/gallery-bg.png');
background-size: 653rpx 453rpx;
background-repeat: no-repeat;
background-position: center;
border-radius: 20rpx;
padding: 20rpx;
box-sizing: border-box;
z-index: 30;
overflow: hidden;
}
/* 上方图片区域 */
.gallery-image-wrapper {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx;
box-sizing: border-box;
}
.gallery-image {
width: 613rpx;
object-fit: cover;
border-radius: 20%;
display: block;
}
.nav-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
}
.prev-btn {
left: -18rpx;
}
.next-btn {
right: -18rpx;
}
.nav-icon {
width: 100%;
height: 100%;
object-fit: contain;
}
/* 响应式设计 */
@media (max-width: 640px) {
.fu-word {
font-size: 1.5rem;
}
.lantern {
font-size: 2rem;
}
.interaction-area {
width: 100px;
height: 80px;
}
}
/* 加载遮罩层 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
z-index: 5;
border-radius: 12rpx;
}
/* 加载动画 */
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
border-top: 4rpx solid #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>