508 lines
12 KiB
Vue
508 lines
12 KiB
Vue
<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>
|