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