qs_xinchun2026_h5/components/DongzhimenScene.vue

554 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, computed } from 'vue'
import FuClickArea from './FuClickArea.vue'
// 组件属性
const props = defineProps({
// 是否为活动状态
active: {
type: Boolean,
default: false
},
// 滚动位置,用于实现视差效果
scrollPosition: {
type: Number,
default: 0
}
})
// 组件事件
const emit = defineEmits(['collect-seal'])
// 是否收集福印
const sealCollected = ref(false)
// 福字点击区域状态
const fuClickAreaVisible = ref(true)
const sq3ImageVisible = ref(false)
// 计算视差效果的偏移量
const parallaxOffset = computed(() => {
// 滚动位置的1/10作为视差偏移
return props.scrollPosition * 0.1
})
// 点击福字区域
const handleFuClick = () => {
fuClickAreaVisible.value = false
sq3ImageVisible.value = true
emit('collect-seal')
}
// 拖拽状态
const isDragging = ref(false)
const showDuck = ref(false)
const deskImage = ref('/static/dzm/img_desk1.png')
const showGuideElements = ref(true)
// 鸭子元素引用
const duckElement = ref(null)
// 鸭子当前位置(使用普通变量,避免响应式带来的性能开销)
let duckX = 0
let duckY = 0
// 拖拽开始区域 (540, 1781, 100, 100) 的中心点
const dragStartArea = { x: 590, y: 1831 }
// 目标区域 (餐桌区域: 13, 1665, 441, 382)
const targetArea = { x: 100, y: 1750, width: 300, height: 200 }
// 开始拖拽
const startDrag = (e) => {
e.preventDefault()
// 如果已经在拖拽中,先结束之前的
if (isDragging.value) {
endDrag()
}
isDragging.value = true
showDuck.value = true
// 获取触摸或鼠标位置
const clientX = e.touches ? e.touches[0].clientX : e.clientX
const clientY = e.touches ? e.touches[0].clientY : e.clientY
// 设置鸭子初始位置
updateDuckPosition(clientX, clientY)
// 先移除可能存在的旧监听器,防止重复绑定
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', endDrag)
document.removeEventListener('touchmove', onDrag)
document.removeEventListener('touchend', endDrag)
// 添加全局事件监听
document.addEventListener('mousemove', onDrag)
document.addEventListener('mouseup', endDrag)
document.addEventListener('touchmove', onDrag, { passive: false })
document.addEventListener('touchend', endDrag)
}
// 拖拽中
const onDrag = (e) => {
if (!isDragging.value) return
e.preventDefault()
const clientX = e.touches ? e.touches[0].clientX : e.clientX
const clientY = e.touches ? e.touches[0].clientY : e.clientY
updateDuckPosition(clientX, clientY)
}
// 更新鸭子位置 - 直接操作 DOM 实现硬件加速
const updateDuckPosition = (clientX, clientY) => {
duckX = clientX
duckY = clientY
if (duckElement.value) {
// 使用 transform3d 启用 GPU 加速
duckElement.value.style.transform = `translate3d(${clientX}px, ${clientY}px, 0) translate(-50%, -50%) scale(1.2)`
}
}
// 结束拖拽
const endDrag = () => {
if (!isDragging.value) return
// 获取容器在视口中的位置
const container = document.querySelector('.dongzhimen-scene-container')
if (container) {
const rect = container.getBoundingClientRect()
// 鸭子相对于容器的位置
const duckRelX = duckX - rect.left
const duckRelY = duckY - rect.top
// 将 rpx 转换为 px (假设设计稿宽度 750rpx实际宽度通过 rect.width 计算)
const rpxToPx = rect.width / 750
const targetX = targetArea.x * rpxToPx
const targetY = targetArea.y * rpxToPx
const targetW = targetArea.width * rpxToPx
const targetH = targetArea.height * rpxToPx
const inTargetArea = (
duckRelX >= targetX &&
duckRelX <= targetX + targetW &&
duckRelY >= targetY &&
duckRelY <= targetY + targetH
)
if (inTargetArea) {
// 更换餐桌图片
deskImage.value = '/static/dzm/img_desk2.png'
// 隐藏引导元素
showGuideElements.value = false
}
}
// 隐藏鸭子
showDuck.value = false
isDragging.value = false
// 移除全局事件监听
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', endDrag)
document.removeEventListener('touchmove', onDrag)
document.removeEventListener('touchend', endDrag)
}
// 页面挂载时的初始化
onMounted(() => {
// 添加动画类,触发入场动画
const container = document.querySelector('.dongzhimen-scene-container')
if (container) {
container.classList.add('animate-in')
}
})
</script>
<template>
<section class="dongzhimen-scene-container" :class="{ 'active': active }">
<!-- 背景图片层 -->
<div class="background-layer" :style="{ transform: `translateY(${parallaxOffset}px)` }">
<!-- 使用东直门商圈背景图片 -->
<img src="/static/bg/bg5.jpg" alt="东直门商圈" class="background-image" />
</div>
<!-- 福字点击区域 -->
<FuClickArea
:visible="fuClickAreaVisible"
:x-range="630"
:y-range="400"
:y-start="150"
:fu-width="100"
:fu-height="100"
@click="handleFuClick"
/>
<!-- sq3图片 -->
<img
v-if="sq3ImageVisible"
src="/static/images/sq5.png"
alt="新春祝福"
class="sq-image"
/>
<!-- 装饰图片 -->
<img :src="deskImage" alt="餐桌" class="deco-img desk-img" />
<img src="/static/dzm/img_stove1.png" alt="灶台" class="deco-img stove-img" />
<img v-if="showGuideElements" src="/static/dzm/img_line.png" alt="线条" class="deco-img line-img" />
<img v-if="showGuideElements" src="/static/images/icon_hand.png" alt="手势" class="deco-img hand-img" />
<!-- 拖拽触发区域 -->
<div
v-if="showGuideElements"
class="drag-trigger-area"
@mousedown="startDrag"
@touchstart="startDrag"
></div>
<!-- 跟随拖拽的鸭子图片 -->
<img
v-show="showDuck"
src="/static/dzm/img_duck.png"
alt="烤鸭"
class="drag-duck"
ref="duckElement"
/>
</section>
</template>
<style scoped>
.dongzhimen-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); }
}
/* 入场动画 */
.dongzhimen-scene-container.animate-in {
animation: sceneFadeIn 1s ease-out;
}
@keyframes sceneFadeIn {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
/* sq图片 */
.sq-image {
position: absolute;
top: 220rpx;
right: -6rpx;
width: auto;
height: auto;
max-width: 300rpx;
z-index: 20;
animation: fadeIn 0.5s ease;
}
/* 装饰图片 */
.deco-img {
position: absolute;
z-index: 25;
}
.desk-img {
left: 13rpx;
top: 1665rpx;
width: 441rpx;
height: 382rpx;
}
.stove-img {
left: 492rpx;
top: 1711rpx;
width: 241rpx;
height: 363rpx;
}
.line-img {
left: 250rpx;
top: 1842rpx;
width: 360rpx;
height: 80rpx;
}
.hand-img {
left: 440rpx;
top: 1900rpx;
width: 38rpx;
height: 40rpx;
animation: arcSlideLeft 1.2s ease-in-out infinite;
}
/* 向左弧形滑动动效 */
@keyframes arcSlideLeft {
0% {
transform: translateX(0) translateY(0);
opacity: 1;
}
100% {
transform: translateX(-80rpx) translateY(3rpx);
opacity: 1;
}
}
/* 拖拽触发区域 */
.drag-trigger-area {
position: absolute;
left: 540rpx;
top: 1781rpx;
width: 100rpx;
height: 100rpx;
cursor: grab;
z-index: 30;
}
.drag-trigger-area:active {
cursor: grabbing;
}
/* 跟随拖拽的鸭子图片 */
.drag-duck {
position: fixed;
left: 0;
top: 0;
width: 54rpx;
height: 105rpx;
opacity: 0.8;
pointer-events: none;
z-index: 1000;
will-change: transform;
}
/* 响应式设计 */
@media (max-width: 640px) {
.fu-word {
font-size: 1.5rem;
}
.lantern {
font-size: 2rem;
}
.interaction-area {
width: 100px;
height: 80px;
}
}
</style>