qs_xinchun2026_h5/components/QianmenScene.vue

532 lines
11 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, onUnmounted, computed, watch } from 'vue'
// 组件属性
const props = defineProps({
// 是否为活动状态
active: {
type: Boolean,
default: false
},
// 滚动位置,用于实现视差效果
scrollPosition: {
type: Number,
default: 0
}
})
// 组件事件
const emit = defineEmits(['collect-seal', 'height-changed'])
// 是否收集福印
const sealCollected = ref(false)
// 音乐播放状态
const isMusicPlaying = ref(false)
const musicPlayer = ref(null)
// 福字点击区域状态
const sq1ImageVisible = 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 toggleMusic = () => {
// 第一次点击音乐按钮时收集福印
if (!sealCollected.value) {
sealCollected.value = true
sq1ImageVisible.value = true
emit('collect-seal')
}
if (!musicPlayer.value) {
// 创建音频对象
musicPlayer.value = uni.createInnerAudioContext()
musicPlayer.value.src = '/static/music/bgm1.mp3'
musicPlayer.value.loop = false
// 监听播放结束
musicPlayer.value.onEnded(() => {
isMusicPlaying.value = false
})
// 监听错误
musicPlayer.value.onError((err) => {
console.error('音乐播放失败:', err)
isMusicPlaying.value = false
showToast({
message: '音乐播放失败',
icon: 'error',
duration: 1500
})
})
}
if (isMusicPlaying.value) {
// 正在播放,点击后停止
musicPlayer.value.stop()
isMusicPlaying.value = false
} else {
// 未播放,点击后开始播放
musicPlayer.value.play()
isMusicPlaying.value = true
}
}
// 页面挂载时的初始化
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)
}
}
// 组件卸载时清理
onUnmounted(() => {
if (musicPlayer.value) {
musicPlayer.value.stop()
musicPlayer.value.destroy()
musicPlayer.value = null
}
})
</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/bg/bg1.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>
<!-- 音乐控制按钮 -->
<div class="music-control" @click="toggleMusic">
<img
:src="isMusicPlaying ? '/static/images/icon_music2.png' : '/static/images/icon_music1.png'"
alt="音乐控制"
class="music-icon"
:class="{ 'playing': isMusicPlaying }"
/>
</div>
<!-- sq1图片 -->
<img
v-if="sq1ImageVisible"
src="/static/images/sq1.png"
alt="新春祝福"
class="sq1-image"
/>
<!-- 舞狮动画 -->
<div class="lion-dance"></div>
</section>
</template>
<style scoped>
.qianmen-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;
}
/* 大鼓交互区域 */
.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); }
}
/* 音乐控制按钮 */
.music-control {
position: absolute;
top: 810rpx;
left: 500rpx;
width: 82rpx;
height: 82rpx;
cursor: pointer;
z-index: 30;
transition: all 0.3s ease;
}
.music-control:active {
transform: scale(0.95);
}
.music-icon {
width: 100%;
height: 100%;
transition: all 0.3s ease;
opacity: 1;
animation: scale 1.5s infinite ease-in-out;
}
@keyframes scale {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
}
/* sq1图片 */
.sq1-image {
position: absolute;
top: 220rpx;
right: -6rpx;
width: auto;
height: auto;
max-width: 300rpx;
z-index: 20;
animation: fadeIn 0.5s ease;
}
/* 福印收集标记 */
.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); }
}
/* ========== 舞狮动画(背景图切换) ========== */
.lion-dance {
position: absolute;
left: 0rpx;
top: 760rpx;
width: 425rpx;
height: 475rpx;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
animation: lionDance 0.9s infinite;
z-index: 25;
}
@keyframes lionDance {
0%, 16.66% { background-image: url('/static/animate/lion/lion1.png'); }
16.67%, 33.33% { background-image: url('/static/animate/lion/lion2.png'); }
33.34%, 50% { background-image: url('/static/animate/lion/lion3.png'); }
50.01%, 66.66% { background-image: url('/static/animate/lion/lion4.png'); }
66.67%, 83.33% { background-image: url('/static/animate/lion/lion5.png'); }
83.34%, 100% { background-image: url('/static/animate/lion/lion6.png'); }
}
/* 响应式设计 */
@media (max-width: 640px) {
.fu-word {
font-size: 1.5rem;
}
.lantern {
font-size: 2rem;
}
.drum-interactive-area {
width: 100px;
height: 80px;
}
}
</style>