qs_xinchun2026_h5/components/QianmenScene.vue

454 lines
9.4 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'
// 组件属性
const props = defineProps({
// 是否为活动状态
active: {
type: Boolean,
default: false
},
// 滚动位置,用于实现视差效果
scrollPosition: {
type: Number,
default: 0
}
})
// 组件事件
const emit = defineEmits(['collect-seal', 'play-drum', 'height-changed'])
// 是否收集福印
const sealCollected = ref(false)
// 场景高度
const sceneHeight = ref(0)
// 背景层引用
const backgroundLayerRef = ref(null)
// 人物位置
const characterPosition = ref({
x: 20,
y: 60
})
// 是否显示烟花
const showFireworks = ref(false)
// 监听scrollPosition的变化
watch(() => props.scrollPosition, (newValue, oldValue) => {
console.log('scrollPosition changed:', oldValue, '->', newValue)
console.log('showFireworks:', showFireworks.value)
})
// 计算视差效果的偏移量
const parallaxOffset = computed(() => {
// 滚动位置的1/10作为视差偏移
const offset = props.scrollPosition * 0.1
// 根据滚动位置控制人物移动(向上行走)
// 假设当滚动位置在100-500px时人物向上移动
const scrollRange = 400 // 滚动范围
const moveRange = 30 // 人物移动范围(百分比)
if (props.scrollPosition >= 100 && props.scrollPosition <= 500) {
// 计算人物Y轴位置从60%移动到30%
const progress = (props.scrollPosition - 100) / scrollRange
characterPosition.value.y = 60 - (progress * moveRange)
}
// 根据页面滑动进度显示烟花滑动到300px时显示
showFireworks.value = props.scrollPosition >= 300
return offset
})
// 收集福印
const collectSeal = () => {
if (!sealCollected.value) {
sealCollected.value = true
emit('collect-seal')
showToast({
message: '恭喜获得非遗福印!',
icon: 'success',
duration: 2000
})
}
}
// 播放大鼓音效
const playDrum = () => {
emit('play-drum')
collectSeal()
showToast({
message: '京韵大鼓,非遗福印!',
icon: 'info',
duration: 1500
})
}
// 页面挂载时的初始化
onMounted(() => {
// 添加动画类,触发入场动画
const container = document.querySelector('.qianmen-scene-container')
if (container) {
container.classList.add('animate-in')
}
// 等待图片加载完成后测量高度
const img = backgroundLayerRef.value ? backgroundLayerRef.value.querySelector('.background-image') : null
if (img) {
// 如果图片已经加载完成
if (img.complete) {
calculateHeight()
} else {
// 图片加载完成后测量高度
img.onload = calculateHeight
}
}
})
// 计算场景高度
const calculateHeight = () => {
if (backgroundLayerRef.value) {
sceneHeight.value = backgroundLayerRef.value.offsetHeight
// 将高度发送给父组件
emit('height-changed', sceneHeight.value)
}
}
</script>
<template>
<section class="qianmen-scene-container" :class="{ 'active': active }">
<!-- 背景图片层 -->
<div class="background-layer" ref="backgroundLayerRef" :style="{ transform: `translateY(${parallaxOffset}px)` }">
<!-- 使用复制到public目录的背景图片 -->
<img src="/static/qianmen-bg.jpg" alt="前门商圈" class="background-image" />
</div>
<!-- 增强动效层 -->
<div class="enhancement-layer">
<!-- 灯笼增强动效 -->
<div class="lanterns">
<div class="lantern left-lantern">🏮</div>
<div class="lantern right-lantern">🏮</div>
</div>
<!-- 福字增强动效 -->
<div class="fu-word"></div>
<!-- 点击提示 -->
<div class="click-indicator" :class="{ 'animate-pulse': !sealCollected }">
<div class="pulse-circle"></div>
</div>
</div>
<!-- 大鼓交互区域 -->
<div class="drum-interactive-area" @click="playDrum">
<!-- 覆盖在图片大鼓上的点击区域 -->
</div>
<!-- 烟花效果 -->
<div class="fireworks">
<div class="firework firework-1">🎆</div>
<div class="firework firework-2">🎇</div>
<div class="firework firework-3">🎆</div>
<div class="firework firework-4">🎇</div>
</div>
<!-- 人物元素 -->
<div
class="character"
:style="{
left: `${characterPosition.x}%`,
top: `${characterPosition.y}%`,
transform: `translate(-50%, -50%)`
}"
>
🧧
</div>
<!-- 烟花效果 -->
<div class="fireworks" v-if="showFireworks">
<div class="firework firework-1">🎆</div>
<div class="firework firework-2">🎇</div>
<div class="firework firework-3">🎆</div>
<div class="firework firework-4">🎇</div>
</div>
<!-- 福印收集标记 -->
<div v-if="sealCollected" class="seal-collected-mark">
<div class="seal-icon">🏮</div>
<div class="seal-text">已收集非遗福印</div>
</div>
</section>
</template>
<style scoped>
.qianmen-scene-container {
position: relative;
width: 100%;
height: auto;
min-height: var(--scene-height, 100vh);
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
background-color: #ff6b35;
}
/* 背景图片层 */
.background-layer {
position: relative;
width: 100%;
transition: transform 0.1s ease;
}
.background-image {
width: 100%;
height: auto;
display: block;
}
/* 为背景图片容器添加与图片主色调匹配的背景色,避免在小屏幕上出现黑边 */
.background-layer {
background-color: #ff6b35;
}
/* 增强动效层 */
.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;
}
/* 大鼓交互区域 */
.drum-interactive-area {
position: absolute;
top: 55%;
right: 15%;
width: 120px;
height: 100px;
cursor: pointer;
z-index: 20;
}
/* 响应式调整交互区域位置 */
@media (max-width: 640px) {
.drum-interactive-area {
top: 52%;
right: 10%;
width: 100px;
height: 80px;
}
}
/* 人物元素 */
.character {
position: absolute;
font-size: 3rem;
pointer-events: none;
z-index: 20;
transition: top 0.1s linear;
filter: drop-shadow(0 0 10px rgba(255, 215, 0, 0.5));
animation: walk 1s infinite alternate;
}
@keyframes walk {
0% {
transform: translate(-50%, -50%) scale(1);
}
100% {
transform: translate(-50%, -50%) scale(1.1);
}
}
/* 烟花效果 */
.fireworks {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 20;
animation: fadeIn 0.5s ease;
}
.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); }
}
/* 入场动画 */
.qianmen-scene-container.animate-in {
animation: sceneFadeIn 1s ease-out;
}
@keyframes sceneFadeIn {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
/* 响应式设计 */
@media (max-width: 640px) {
.fu-word {
font-size: 1.5rem;
}
.lantern {
font-size: 2rem;
}
.drum-interactive-area {
width: 100px;
height: 80px;
}
}
</style>