qs_xinchun2026_h5/components/QianmenScene.vue

508 lines
12 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'
import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue'
// 组件属性
const props = defineProps({
// 是否为活动状态
active: {
type: Boolean,
default: false
},
// 滚动位置,用于实现视差效果
scrollPosition: {
type: Number,
default: 0
},
// 视频地址
videoUrl: {
type: String,
default: ''
},
// 全局BGM播放状态
isMusicPlaying: {
type: Boolean,
default: false
},
// 全局视频播放状态
isVideoPlaying: {
type: Boolean,
default: false
},
// webview打开状态
isWebviewOpening: {
type: Boolean,
default: false
}
})
// 组件事件
const emit = defineEmits(['collect-seal', 'height-changed', 'video-open', 'video-close', 'pause-bgm', 'resume-bgm'])
// 是否收集福印
const sealCollected = ref(false)
// 音乐播放状态(由父组件控制)
const musicPlayer = ref(null)
// 鼓声音频状态
const isDrumPlaying = ref(false)
const drumPlayer = ref(null)
const wasBgPlayingBeforeDrum = ref(false)
// 舞狮动画一次性播放状态
const isPlayingOnce = ref(false)
// 福字点击区域状态
const sq1ImageVisible = ref(false)
// 视频播放状态
const showVideoPlayer = 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)
})
// 监听active属性的变化当组件变为非活动状态时停止鼓声
watch(() => props.active, (newActive) => {
if (!newActive && isDrumPlaying.value && drumPlayer.value) {
drumPlayer.value.stop()
isDrumPlaying.value = false
// 恢复BGM播放状态
emit('resume-bgm')
}
})
// 监听视频播放状态的变化,当视频开始播放时停止鼓声
watch(() => props.isVideoPlaying, (newIsVideoPlaying) => {
if (newIsVideoPlaying && isDrumPlaying.value && drumPlayer.value) {
drumPlayer.value.stop()
isDrumPlaying.value = false
// 恢复BGM播放状态
emit('resume-bgm')
}
})
// 监听webview打开状态的变化当webview打开时停止鼓声
watch(() => props.isWebviewOpening, (newIsWebviewOpening) => {
if (newIsWebviewOpening && isDrumPlaying.value && drumPlayer.value) {
drumPlayer.value.stop()
isDrumPlaying.value = false
// 不恢复BGM播放状态由webview返回时处理
}
})
// 计算视差效果的偏移量
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 (!drumPlayer.value) {
// 创建鼓声音频对象
drumPlayer.value = uni.createInnerAudioContext()
const drumUrl = new URL('/static/music/bgm1.mp3', import.meta.url)
drumPlayer.value.src = drumUrl.href
drumPlayer.value.loop = false
// 监听播放结束
drumPlayer.value.onEnded(() => {
isDrumPlaying.value = false
// 恢复BGM播放状态
emit('resume-bgm')
})
// 监听错误
drumPlayer.value.onError((err) => {
console.error('鼓声播放失败:', err)
isDrumPlaying.value = false
// 恢复BGM播放状态
emit('resume-bgm')
uni.showToast({
title: '鼓声播放失败',
icon: 'none',
duration: 1500
})
})
}
if (isDrumPlaying.value) {
// 正在播放,点击后停止
drumPlayer.value.stop()
isDrumPlaying.value = false
// 恢复BGM播放状态
emit('resume-bgm')
} else {
// 保存BGM状态
wasBgPlayingBeforeDrum.value = props.isMusicPlaying
// 暂停BGM
emit('pause-bgm', 'drum')
// 播放鼓声
drumPlayer.value.play()
isDrumPlaying.value = true
}
}
// 页面挂载时的初始化
onMounted(() => {
// 预加载舞狮动画图片
preloadLionDanceImages()
// 添加动画类,触发入场动画
const container = document.querySelector('.qianmen-scene-container')
if (container) {
container.classList.add('animate-in')
}
// 进入页面后自动播放一次舞狮动画
isPlayingOnce.value = true
// 动画播放一次后停止动画时长约0.9秒)
setTimeout(() => {
isPlayingOnce.value = false
}, 900)
// 等待图片加载完成后测量高度
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)
}
}
// 打开视频播放器
const openVideoPlayer = () => {
// 如果鼓声正在播放,停止鼓声
if (isDrumPlaying.value && drumPlayer.value) {
drumPlayer.value.stop()
isDrumPlaying.value = false
drumPlayer.value.destroy()
drumPlayer.value = null
// 恢复BGM播放状态
emit('resume-bgm')
}
showVideoPlayer.value = true
emit('video-open')
}
// 关闭视频播放器
const closeVideoPlayer = () => {
showVideoPlayer.value = false
emit('video-close')
}
// 预加载舞狮动画图片
const preloadLionDanceImages = () => {
const lionImages = [
'/static/animate/lion/lion1.png',
'/static/animate/lion/lion2.png',
'/static/animate/lion/lion3.png',
'/static/animate/lion/lion4.png',
'/static/animate/lion/lion5.png',
'/static/animate/lion/lion6.png'
]
lionImages.forEach((imageUrl) => {
const img = new Image()
img.src = imageUrl
})
}
// 组件卸载时清理
onUnmounted(() => {
if (musicPlayer.value) {
musicPlayer.value.stop()
musicPlayer.value.destroy()
musicPlayer.value = null
}
if (drumPlayer.value) {
drumPlayer.value.stop()
drumPlayer.value.destroy()
drumPlayer.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="music-control" @click="toggleMusic">
<img
:src="isDrumPlaying ? '/static/images/icon_music2.png' : '/static/images/icon_music1.png' "
alt="音乐控制"
class="music-icon"
:class="{ 'playing': isDrumPlaying }"
/>
</div> -->
<!-- 热点点击区域 -->
<div class="hotspot-area" @click="toggleMusic">
<div class="pulse-indicator">
<div class="pulse-circle"></div>
</div>
<div class="hotspot-text">点击播放京韵大鼓</div>
</div>
<!-- sq1图片 -->
<img
v-if="sq1ImageVisible"
src="/static/images/sq1.png"
alt="新春祝福"
class="sq1-image"
/>
<!-- 舞狮动画 -->
<div class="lion-dance" :class="{ 'playing': isDrumPlaying, 'play-once': isPlayingOnce }"></div>
<!-- 视频播放按钮 -->
<VideoPlayButton :position="{ bottom: '30rpx', right: '11rpx' }" @play="openVideoPlayer" />
<!-- 视频播放器弹窗 -->
<VideoPlayerModal :video-url="props.videoUrl" :visible="showVideoPlayer" @close="closeVideoPlayer" />
</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;
}
/* 音乐控制按钮 */
.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;
}
@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;
background-image: url('/static/animate/lion/lion1.png');
z-index: 25;
}
.lion-dance.playing {
animation: lionDance 0.9s infinite;
}
.lion-dance.play-once {
animation: lionDance 0.9s;
}
@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'); }
}
/* 热点点击区域 */
.hotspot-area {
position: absolute;
left: 465rpx;
top: 780rpx;
width: 150rpx;
height: 150rpx;
cursor: pointer;
z-index: 25;
display: flex;
align-items: center;
justify-content: center;
}
/* 脉冲动效 */
.pulse-indicator {
position: relative;
width: 100rpx;
height: 100rpx;
}
.pulse-circle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
border-radius: 50%;
background-color: rgba(255, 215, 0, 0.4);
border: 3rpx solid rgba(255, 215, 0, 0.8);
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: translate(-50%, -50%) scale(0.8);
opacity: 0.8;
}
100% {
transform: translate(-50%, -50%) scale(2);
opacity: 0;
}
}
/* 热点文字提示 */
.hotspot-text {
position: absolute;
bottom: -40rpx;
left: 50%;
transform: translateX(-50%);
font-size: 24rpx;
color: #fff;
background-color: rgba(0, 0, 0, 0.6);
padding: 8rpx 16rpx;
border-radius: 20rpx;
white-space: nowrap;
z-index: 30;
text-align: center;
}
</style>