554 lines
11 KiB
Vue
554 lines
11 KiB
Vue
<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> |