qs_xinchun2026_h5/components/DongzhimenScene.vue

534 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, nextTick, getCurrentInstance as vueGetCurrentInstance } from 'vue'
import FuClickArea from './FuClickArea.vue'
// 获取 Vue 实例
const instance = vueGetCurrentInstance()
// 组件属性
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(() => {
return props.scrollPosition * 0.1
})
// 点击福字区域
const handleFuClick = () => {
fuClickAreaVisible.value = false
sq3ImageVisible.value = true
emit('collect-seal')
}
// 拖拽状态
const isDragging = ref(false)
const showDuck = ref(true) // 调试时默认显示鸭子
const deskImage = ref('/static/dzm/img_desk1.png')
const showGuideElements = ref(true)
// Canvas 上下文
const ctx = ref(null)
// 鸭子图片路径
const duckImagePath = ref(null)
// 鸭子位置(相对于 Canvas
const duckX = ref(275)
const duckY = ref(110)
// 鸭子尺寸(与图片实际尺寸一致)
const duckWidth = 36
const duckHeight = 70
// 拖拽偏移量
const dragOffsetX = ref(0)
const dragOffsetY = ref(0)
// Canvas 位置信息
const canvasRect = ref({
left: 0,
top: 0,
width: 0,
height: 0
})
// 目标区域 (餐桌区域) - 相对于 Canvas 的坐标
const targetArea = ref({
x: 0,
y: 80,
width: 200,
height: 150
})
// 动画帧 ID
const animationId = ref(null)
// 初始化 Canvas
const initCanvas = () => {
console.log('正在初始化 Canvas...')
// 在 uni-app 中使用 uni.createCanvasContext
ctx.value = uni.createCanvasContext('dragDuckCanvas', instance)
if (ctx.value) {
console.log('Canvas 上下文创建成功')
// 加载鸭子图片
loadDuckImage()
// 获取 Canvas 位置
getCanvasPosition()
// 开始动画循环
startAnimation()
} else {
console.error('Canvas 上下文创建失败')
}
}
// 加载鸭子图片
const loadDuckImage = () => {
uni.getImageInfo({
src: '/static/dzm/img_duck.png',
success: (res) => {
console.log('鸭子图片加载成功:', res)
duckImagePath.value = res.path
},
fail: (err) => {
console.error('鸭子图片加载失败:', err)
}
})
}
// 获取 Canvas 元素位置
const getCanvasPosition = () => {
const query = uni.createSelectorQuery().in(instance)
query.select('#dragDuckCanvas').boundingClientRect(res => {
if (res) {
canvasRect.value = {
left: res.left,
top: res.top,
width: res.width,
height: res.height
}
console.log('Canvas 位置信息:', canvasRect.value)
} else {
console.error('无法获取 Canvas 位置信息')
}
}).exec()
}
// 开始动画循环
const startAnimation = () => {
const animate = () => {
drawCanvas()
animationId.value = requestAnimationFrame(animate)
}
animationId.value = requestAnimationFrame(animate)
}
// 绘制 Canvas
const drawCanvas = () => {
if (!ctx.value) return
// 清除 Canvas
ctx.value.clearRect(0, 0, canvasRect.value.width || 400, canvasRect.value.height || 600)
// 绘制目标区域
drawTarget()
// 绘制鸭子
drawDuck()
// 执行绘制
ctx.value.draw(true)
}
// 绘制目标区域
const drawTarget = () => {
const target = targetArea.value
// 绘制目标区域背景
ctx.value.setFillStyle('rgba(52, 152, 219, 0.3)')
ctx.value.fillRect(target.x, target.y, target.width, target.height)
// 绘制目标区域边框
ctx.value.setStrokeStyle('#3498db')
ctx.value.setLineWidth(2)
ctx.value.setLineDash([5, 5])
ctx.value.strokeRect(target.x, target.y, target.width, target.height)
ctx.value.setLineDash([])
// 绘制目标区域文字
ctx.value.setFontSize(14)
ctx.value.setFillStyle('#3498db')
ctx.value.setTextAlign('center')
ctx.value.fillText(
'目标区域',
target.x + target.width / 2,
target.y + target.height / 2
)
}
// 绘制鸭子
const drawDuck = () => {
if (!showDuck.value) return
// 如果图片已加载,绘制图片
if (duckImagePath.value) {
ctx.value.drawImage(duckImagePath.value, duckX.value, duckY.value, duckWidth, duckHeight)
} else {
// 图片未加载时,绘制占位方块
ctx.value.setFillStyle('#ffcc00')
ctx.value.fillRect(duckX.value, duckY.value, duckWidth, duckHeight)
// 绘制边框
ctx.value.setStrokeStyle('#333')
ctx.value.setLineWidth(2)
ctx.value.strokeRect(duckX.value, duckY.value, duckWidth, duckHeight)
// 绘制文字
ctx.value.setFontSize(14)
ctx.value.setFillStyle('#FFFFFF')
ctx.value.setTextAlign('center')
ctx.value.setTextBaseline('middle')
ctx.value.fillText('鸭', duckX.value + duckWidth / 2, duckY.value + duckHeight / 2)
}
// 如果正在拖拽,绘制拖拽效果
if (isDragging.value) {
// 绘制拖拽阴影
ctx.value.setFillStyle('rgba(231, 76, 60, 0.3)')
ctx.value.fillRect(duckX.value, duckY.value, duckWidth, duckHeight)
// 绘制拖拽边框
ctx.value.setStrokeStyle('#e74c3c')
ctx.value.setLineWidth(2)
ctx.value.setLineDash([5, 5])
ctx.value.strokeRect(duckX.value - 3, duckY.value - 3, duckWidth + 6, duckHeight + 6)
ctx.value.setLineDash([])
}
}
// 检查触摸点是否在鸭子内
const checkTouchInDuck = (x, y) => {
return x >= duckX.value &&
x <= duckX.value + duckWidth &&
y >= duckY.value &&
y <= duckY.value + duckHeight
}
// 检查鸭子是否在目标区域内
const checkDuckInTarget = () => {
const target = targetArea.value
// 计算鸭子中心点
const duckCenterX = duckX.value + duckWidth / 2
const duckCenterY = duckY.value + duckHeight / 2
// 检查中心点是否在目标区域内
return duckCenterX >= target.x &&
duckCenterX <= target.x + target.width &&
duckCenterY >= target.y &&
duckCenterY <= target.y + target.height
}
// 触摸开始事件
const handleTouchStart = (e) => {
console.log('触摸开始事件触发', e)
// 获取触摸点
const touch = e.touches[0]
if (!touch) {
console.error('没有触摸点信息')
return
}
// 确保 Canvas 位置信息是最新的
getCanvasPosition()
// 异步获取 Canvas 位置后执行检查
setTimeout(() => {
// 计算相对于 Canvas 的坐标
const touchX = touch.clientX - canvasRect.value.left
const touchY = touch.clientY - canvasRect.value.top
console.log(`触摸点相对 Canvas: (${touchX}, ${touchY})`)
console.log(`鸭子位置: (${duckX.value}, ${duckY.value})`)
// 检查是否点击在鸭子上
if (checkTouchInDuck(touchX, touchY)) {
console.log('触摸点在鸭子内,开始拖拽')
// 设置拖拽状态和偏移量
isDragging.value = true
dragOffsetX.value = touchX - duckX.value
dragOffsetY.value = touchY - duckY.value
showDuck.value = true
} else {
console.log('触摸点不在鸭子内')
}
}, 50)
}
// 触摸移动事件
const handleTouchMove = (e) => {
if (!isDragging.value) return
e.preventDefault()
const touch = e.touches[0]
if (!touch) return
// 计算相对于 Canvas 的坐标
const touchX = touch.clientX - canvasRect.value.left
const touchY = touch.clientY - canvasRect.value.top
// 更新鸭子位置
duckX.value = touchX - dragOffsetX.value
duckY.value = touchY - dragOffsetY.value
// 限制边界
const canvasW = canvasRect.value.width || 400
const canvasH = canvasRect.value.height || 600
duckX.value = Math.max(0, Math.min(canvasW - duckWidth, duckX.value))
duckY.value = Math.max(0, Math.min(canvasH - duckHeight, duckY.value))
console.log(`鸭子新位置: (${duckX.value}, ${duckY.value})`)
}
// 触摸结束事件
const handleTouchEnd = () => {
console.log('触摸结束事件触发')
if (isDragging.value) {
isDragging.value = false
// 检查是否成功放入目标区域
if (checkDuckInTarget()) {
console.log('成功放入目标区域!')
// 更换餐桌图片
deskImage.value = '/static/dzm/img_desk2.png'
// 隐藏引导元素
showGuideElements.value = false
// 隐藏鸭子
showDuck.value = false
}
}
}
// 开始拖拽(从触发区域开始)
const startDrag = (e) => {
console.log('开始拖拽', e)
// 初始化 Canvas
if (!ctx.value) {
initCanvas()
}
// 设置鸭子初始位置(在触发区域附近)
duckX.value = 50
duckY.value = 50
showDuck.value = true
// 触发触摸开始事件处理
handleTouchStart(e)
}
// 页面挂载时的初始化
onMounted(() => {
// 添加动画类,触发入场动画
const container = document.querySelector('.dongzhimen-scene-container')
if (container) {
container.classList.add('animate-in')
}
// 调试时自动初始化 Canvas
setTimeout(() => {
initCanvas()
}, 500)
})
// 组件卸载时清理
onUnmounted(() => {
// 停止动画
if (animationId.value) {
cancelAnimationFrame(animationId.value)
}
})
</script>
<template>
<view class="dongzhimen-scene-container" :class="{ 'active': active }">
<!-- 背景图片层 -->
<view class="background-layer" :style="{ transform: `translateY(${parallaxOffset}px)` }">
<image src="/static/bg/bg5.jpg" alt="东直门商圈" class="background-image" mode="widthFix" />
</view>
<!-- 福字点击区域 -->
<FuClickArea
:visible="fuClickAreaVisible"
:x-range="630"
:y-range="400"
:y-start="150"
:fu-width="100"
:fu-height="100"
@click="handleFuClick"
/>
<!-- sq3图片 -->
<image
v-if="sq3ImageVisible"
src="/static/images/sq5.png"
alt="新春祝福"
class="sq-image"
mode="widthFix"
/>
<!-- 装饰图片 -->
<image :src="deskImage" alt="餐桌" class="deco-img desk-img" mode="widthFix" />
<image src="/static/dzm/img_stove1.png" alt="灶台" class="deco-img stove-img" mode="widthFix" />
<image v-if="showGuideElements" src="/static/dzm/img_line.png" alt="线条" class="deco-img line-img" mode="widthFix" />
<image v-if="showGuideElements" src="/static/images/icon_hand.png" alt="手势" class="deco-img hand-img" mode="widthFix" />
<!-- Canvas 拖拽区域 -->
<canvas
canvas-id="dragDuckCanvas"
id="dragDuckCanvas"
class="drag-canvas"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
></canvas>
<!-- 拖拽触发区域 -->
<view
v-if="showGuideElements"
class="drag-trigger-area"
@touchstart="startDrag"
></view>
</view>
</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;
}
.sq-image {
position: absolute;
top: 220rpx;
right: -6rpx;
width: 300rpx;
height: auto;
z-index: 20;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
.deco-img {
position: absolute;
z-index: 25;
}
.desk-img {
left: 13rpx;
top: 1665rpx;
width: 441rpx;
height: auto;
}
.stove-img {
left: 492rpx;
top: 1711rpx;
width: 241rpx;
height: auto;
}
.line-img {
left: 250rpx;
top: 1842rpx;
width: 360rpx;
height: auto;
}
.hand-img {
left: 440rpx;
top: 1900rpx;
width: 38rpx;
height: auto;
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-canvas {
position: absolute;
left: 0;
top: 800px;
width: 375px;
height: 300px;
z-index: 100;
pointer-events: auto;
}
.drag-trigger-area {
position: absolute;
left: 540rpx;
top: 1781rpx;
width: 100rpx;
height: 100rpx;
z-index: 30;
}
</style>