1、增加调试模式,调试模式下显示鸭子范围和目标区域范围
2、canvas中所有元素定位需要使用rpx转换处理
3、鸭子放开后,需要自动恢复原来位置
This commit is contained in:
Wenzhe 2026-01-31 21:19:18 +08:00
parent c0897a3d86
commit fd020b935d
1 changed files with 194 additions and 65 deletions

View File

@ -44,25 +44,48 @@ const showDuck = ref(true) // 调试时默认显示鸭子
const deskImage = ref('/static/dzm/img_desk1.png') const deskImage = ref('/static/dzm/img_desk1.png')
const showGuideElements = ref(true) const showGuideElements = ref(true)
// Canvas 触摸是否禁用
const canvasDisabled = ref(false)
// 调试模式
const debugMode = ref(false)
// Canvas 上下文 // Canvas 上下文
const ctx = ref(null) const ctx = ref(null)
// 鸭子图片路径 // 鸭子图片路径
const duckImagePath = ref(null) const duckImagePath = ref(null)
// 鸭子位置(相对于 Canvas) // rpx 转换比例
const duckX = ref(275) const rpxRatio = ref(0.5) // 默认 iPhone 6/7/8 的 375px / 750rpx = 0.5
const duckY = ref(110)
// 鸭子尺寸(与图片实际尺寸一致) // 鸭子位置和尺寸(使用 rpx 单位,在 750rpx 设计稿下)
const duckWidth = 36 const duckRpx = ref({
const duckHeight = 70 x: 550, // 275px * 2 = 550rpx
y: 220, // 110px * 2 = 220rpx
width: 72, // 36px * 2 = 72rpx
height: 140 // 70px * 2 = 140rpx
})
// 鸭子当前位置(px 单位,用于绘制)
const duckX = ref(0)
const duckY = ref(0)
const duckWidth = ref(0)
const duckHeight = ref(0)
// 拖拽偏移量 // 拖拽偏移量
const dragOffsetX = ref(0) const dragOffsetX = ref(0)
const dragOffsetY = ref(0) const dragOffsetY = ref(0)
// Canvas 位置信息 // Canvas 位置和尺寸(使用 rpx 单位)
const canvasRpx = ref({
x: 0,
y: 1550, // 800px * 2 = 1600rpx
width: 750, // 375px * 2 = 750rpx (全屏宽度)
height: 600 // 300px * 2 = 600rpx
})
// Canvas 当前位置和尺寸(px 单位)
const canvasRect = ref({ const canvasRect = ref({
left: 0, left: 0,
top: 0, top: 0,
@ -70,12 +93,20 @@ const canvasRect = ref({
height: 0 height: 0
}) })
// 目标区域 (餐桌区域) - 相对于 Canvas 的坐标 // 目标区域 (餐桌区域) - 使用 rpx 单位
const targetAreaRpx = ref({
x: 0,
y: 160, // 80px * 2 = 160rpx
width: 400, // 200px * 2 = 400rpx
height: 300 // 150px * 2 = 300rpx
})
// 目标区域当前位置和尺寸(px 单位)
const targetArea = ref({ const targetArea = ref({
x: 0, x: 0,
y: 80, y: 0,
width: 200, width: 0,
height: 150 height: 0
}) })
// 动画帧 ID // 动画帧 ID
@ -115,20 +146,70 @@ const loadDuckImage = () => {
}) })
} }
// rpx 转 px
const rpxToPx = (rpx) => {
return rpx * rpxRatio.value
}
// 更新鸭子位置(根据 rpx 计算 px)
const updateDuckPosition = () => {
duckX.value = rpxToPx(duckRpx.value.x)
duckY.value = rpxToPx(duckRpx.value.y)
duckWidth.value = rpxToPx(duckRpx.value.width)
duckHeight.value = rpxToPx(duckRpx.value.height)
}
// 更新目标区域(根据 rpx 计算 px)
const updateTargetArea = () => {
targetArea.value = {
x: rpxToPx(targetAreaRpx.value.x),
y: rpxToPx(targetAreaRpx.value.y),
width: rpxToPx(targetAreaRpx.value.width),
height: rpxToPx(targetAreaRpx.value.height)
}
}
// 更新 Canvas 位置和尺寸(根据 rpx 计算 px)
const updateCanvasRect = () => {
canvasRect.value = {
left: rpxToPx(canvasRpx.value.x),
top: rpxToPx(canvasRpx.value.y),
width: rpxToPx(canvasRpx.value.width),
height: rpxToPx(canvasRpx.value.height)
}
}
// Canvas 在视口中的实际位置(用于触摸计算)
const canvasBoundingRect = ref({
left: 0,
top: 0
})
// 获取 Canvas 元素位置 // 获取 Canvas 元素位置
const getCanvasPosition = () => { const getCanvasPosition = () => {
// 计算 rpx 比例:屏幕宽度 / 750
const systemInfo = uni.getSystemInfoSync()
rpxRatio.value = systemInfo.windowWidth / 750
console.log('屏幕宽度:', systemInfo.windowWidth, 'rpxRatio:', rpxRatio.value)
// 更新 Canvas、鸭子和目标区域的 px 值
updateCanvasRect()
updateDuckPosition()
updateTargetArea()
console.log('Canvas 位置信息:', canvasRect.value)
}
// 获取 Canvas 在视口中的实际位置(用于触摸坐标计算)
const getCanvasBoundingRect = () => {
const query = uni.createSelectorQuery().in(instance) const query = uni.createSelectorQuery().in(instance)
query.select('#dragDuckCanvas').boundingClientRect(res => { query.select('#dragDuckCanvas').boundingClientRect(res => {
if (res) { if (res) {
canvasRect.value = { canvasBoundingRect.value = {
left: res.left, left: res.left,
top: res.top, top: res.top
width: res.width,
height: res.height
} }
console.log('Canvas 位置信息:', canvasRect.value) console.log('Canvas 视口位置:', canvasBoundingRect.value)
} else {
console.error('无法获取 Canvas 位置信息')
} }
}).exec() }).exec()
} }
@ -163,6 +244,8 @@ const drawCanvas = () => {
const drawTarget = () => { const drawTarget = () => {
const target = targetArea.value const target = targetArea.value
if (debugMode.value) {
// 调试模式:显示目标区域边框和文字
// 绘制目标区域背景 // 绘制目标区域背景
ctx.value.setFillStyle('rgba(52, 152, 219, 0.3)') ctx.value.setFillStyle('rgba(52, 152, 219, 0.3)')
ctx.value.fillRect(target.x, target.y, target.width, target.height) ctx.value.fillRect(target.x, target.y, target.width, target.height)
@ -183,44 +266,61 @@ const drawTarget = () => {
target.x + target.width / 2, target.x + target.width / 2,
target.y + target.height / 2 target.y + target.height / 2
) )
}
// 非调试模式:完全透明,不绘制任何内容
} }
// 绘制鸭子 // 绘制鸭子
const drawDuck = () => { const drawDuck = () => {
if (!showDuck.value) return if (!showDuck.value) return
// 非调试模式下,只有拖拽时才显示鸭子
if (!debugMode.value && !isDragging.value) {
return
}
// 如果正在拖拽,设置半透明
if (isDragging.value) {
ctx.value.globalAlpha = 0.6
} else {
ctx.value.globalAlpha = 1.0
}
// 如果图片已加载,绘制图片 // 如果图片已加载,绘制图片
if (duckImagePath.value) { if (duckImagePath.value) {
ctx.value.drawImage(duckImagePath.value, duckX.value, duckY.value, duckWidth, duckHeight) ctx.value.drawImage(duckImagePath.value, duckX.value, duckY.value, duckWidth.value, duckHeight.value)
} else { } else {
// 图片未加载时,绘制占位方块 // 图片未加载时,绘制占位方块
ctx.value.setFillStyle('#ffcc00') ctx.value.setFillStyle('#ffcc00')
ctx.value.fillRect(duckX.value, duckY.value, duckWidth, duckHeight) ctx.value.fillRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value)
// 绘制边框 // 绘制边框
ctx.value.setStrokeStyle('#333') ctx.value.setStrokeStyle('#333')
ctx.value.setLineWidth(2) ctx.value.setLineWidth(2)
ctx.value.strokeRect(duckX.value, duckY.value, duckWidth, duckHeight) ctx.value.strokeRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value)
// 绘制文字 // 绘制文字
ctx.value.setFontSize(14) ctx.value.setFontSize(14)
ctx.value.setFillStyle('#FFFFFF') ctx.value.setFillStyle('#FFFFFF')
ctx.value.setTextAlign('center') ctx.value.setTextAlign('center')
ctx.value.setTextBaseline('middle') ctx.value.setTextBaseline('middle')
ctx.value.fillText('鸭', duckX.value + duckWidth / 2, duckY.value + duckHeight / 2) ctx.value.fillText('鸭', duckX.value + duckWidth.value / 2, duckY.value + duckHeight.value / 2)
} }
// 如果正在拖拽,绘制拖拽效果 // 恢复透明度
if (isDragging.value) { ctx.value.globalAlpha = 1.0
// 调试模式下显示拖拽效果红框
if (debugMode.value && isDragging.value) {
// 绘制拖拽阴影 // 绘制拖拽阴影
ctx.value.setFillStyle('rgba(231, 76, 60, 0.3)') ctx.value.setFillStyle('rgba(231, 76, 60, 0.3)')
ctx.value.fillRect(duckX.value, duckY.value, duckWidth, duckHeight) ctx.value.fillRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value)
// 绘制拖拽边框 // 绘制拖拽边框
ctx.value.setStrokeStyle('#e74c3c') ctx.value.setStrokeStyle('#e74c3c')
ctx.value.setLineWidth(2) ctx.value.setLineWidth(2)
ctx.value.setLineDash([5, 5]) ctx.value.setLineDash([5, 5])
ctx.value.strokeRect(duckX.value - 3, duckY.value - 3, duckWidth + 6, duckHeight + 6) ctx.value.strokeRect(duckX.value - 3, duckY.value - 3, duckWidth.value + 6, duckHeight.value + 6)
ctx.value.setLineDash([]) ctx.value.setLineDash([])
} }
} }
@ -228,9 +328,9 @@ const drawDuck = () => {
// 检查触摸点是否在鸭子内 // 检查触摸点是否在鸭子内
const checkTouchInDuck = (x, y) => { const checkTouchInDuck = (x, y) => {
return x >= duckX.value && return x >= duckX.value &&
x <= duckX.value + duckWidth && x <= duckX.value + duckWidth.value &&
y >= duckY.value && y >= duckY.value &&
y <= duckY.value + duckHeight y <= duckY.value + duckHeight.value
} }
// 检查鸭子是否在目标区域内 // 检查鸭子是否在目标区域内
@ -238,8 +338,8 @@ const checkDuckInTarget = () => {
const target = targetArea.value const target = targetArea.value
// 计算鸭子中心点 // 计算鸭子中心点
const duckCenterX = duckX.value + duckWidth / 2 const duckCenterX = duckX.value + duckWidth.value / 2
const duckCenterY = duckY.value + duckHeight / 2 const duckCenterY = duckY.value + duckHeight.value / 2
// 检查中心点是否在目标区域内 // 检查中心点是否在目标区域内
return duckCenterX >= target.x && return duckCenterX >= target.x &&
@ -250,6 +350,12 @@ const checkDuckInTarget = () => {
// 触摸开始事件 // 触摸开始事件
const handleTouchStart = (e) => { const handleTouchStart = (e) => {
// 如果 Canvas 已禁用,不处理触摸事件
if (canvasDisabled.value) {
console.log('Canvas 已禁用,忽略触摸事件')
return
}
console.log('触摸开始事件触发', e) console.log('触摸开始事件触发', e)
// 获取触摸点 // 获取触摸点
@ -259,17 +365,20 @@ const handleTouchStart = (e) => {
return return
} }
// 确保 Canvas 位置信息是最新的 // 获取 Canvas 在视口中的实际位置
getCanvasPosition() getCanvasBoundingRect()
// 异步获取 Canvas 位置后执行检查 // 异步获取 Canvas 位置后执行检查
setTimeout(() => { setTimeout(() => {
// 计算相对于 Canvas 的坐标 // 计算相对于 Canvas 的坐标(使用视口位置)
const touchX = touch.clientX - canvasRect.value.left const touchX = touch.clientX - canvasBoundingRect.value.left
const touchY = touch.clientY - canvasRect.value.top const touchY = touch.clientY - canvasBoundingRect.value.top
console.log(`触摸点 client: (${touch.clientX}, ${touch.clientY})`)
console.log(`Canvas 视口位置: (${canvasBoundingRect.value.left}, ${canvasBoundingRect.value.top})`)
console.log(`触摸点相对 Canvas: (${touchX}, ${touchY})`) console.log(`触摸点相对 Canvas: (${touchX}, ${touchY})`)
console.log(`鸭子位置: (${duckX.value}, ${duckY.value})`) console.log(`鸭子位置: (${duckX.value}, ${duckY.value})`)
console.log(`鸭子尺寸: (${duckWidth.value}, ${duckHeight.value})`)
// 检查是否点击在鸭子上 // 检查是否点击在鸭子上
if (checkTouchInDuck(touchX, touchY)) { if (checkTouchInDuck(touchX, touchY)) {
@ -296,9 +405,9 @@ const handleTouchMove = (e) => {
const touch = e.touches[0] const touch = e.touches[0]
if (!touch) return if (!touch) return
// 计算相对于 Canvas 的坐标 // 计算相对于 Canvas 的坐标(使用视口位置)
const touchX = touch.clientX - canvasRect.value.left const touchX = touch.clientX - canvasBoundingRect.value.left
const touchY = touch.clientY - canvasRect.value.top const touchY = touch.clientY - canvasBoundingRect.value.top
// 更新鸭子位置 // 更新鸭子位置
duckX.value = touchX - dragOffsetX.value duckX.value = touchX - dragOffsetX.value
@ -308,8 +417,8 @@ const handleTouchMove = (e) => {
const canvasW = canvasRect.value.width || 400 const canvasW = canvasRect.value.width || 400
const canvasH = canvasRect.value.height || 600 const canvasH = canvasRect.value.height || 600
duckX.value = Math.max(0, Math.min(canvasW - duckWidth, duckX.value)) duckX.value = Math.max(0, Math.min(canvasW - duckWidth.value, duckX.value))
duckY.value = Math.max(0, Math.min(canvasH - duckHeight, duckY.value)) duckY.value = Math.max(0, Math.min(canvasH - duckHeight.value, duckY.value))
console.log(`鸭子新位置: (${duckX.value}, ${duckY.value})`) console.log(`鸭子新位置: (${duckX.value}, ${duckY.value})`)
} }
@ -330,10 +439,27 @@ const handleTouchEnd = () => {
showGuideElements.value = false showGuideElements.value = false
// 隐藏鸭子 // 隐藏鸭子
showDuck.value = false showDuck.value = false
// 禁用 Canvas 触摸事件
canvasDisabled.value = true
console.log('Canvas 触摸事件已禁用')
} else {
// 未放入目标区域,回归到原始位置
console.log('未放入目标区域,回归原始位置')
resetDuckPosition()
} }
} }
} }
// 重置鸭子位置到初始值
const resetDuckPosition = () => {
// 恢复原始 rpx 值
duckRpx.value.x = 550
duckRpx.value.y = 220
// 更新 px 值
updateDuckPosition()
console.log('鸭子已回归原始位置:', duckX.value, duckY.value)
}
// 开始拖拽(从触发区域开始) // 开始拖拽(从触发区域开始)
const startDrag = (e) => { const startDrag = (e) => {
console.log('开始拖拽', e) console.log('开始拖拽', e)
@ -343,9 +469,10 @@ const startDrag = (e) => {
initCanvas() initCanvas()
} }
// 设置鸭子初始位置(在触发区域附近) // 设置鸭子初始位置(使用 rpx 值,会自动转换为 px)
duckX.value = 50 duckRpx.value.x = 100 // 100rpx 对应约 50px (iPhone 6/7/8)
duckY.value = 50 duckRpx.value.y = 100
updateDuckPosition()
showDuck.value = true showDuck.value = true
// 触发触摸开始事件处理 // 触发触摸开始事件处理
@ -413,6 +540,12 @@ onUnmounted(() => {
canvas-id="dragDuckCanvas" canvas-id="dragDuckCanvas"
id="dragDuckCanvas" id="dragDuckCanvas"
class="drag-canvas" class="drag-canvas"
:style="{
left: canvasRect.left + 'px',
top: canvasRect.top + 'px',
width: canvasRect.width + 'px',
height: canvasRect.height + 'px'
}"
@touchstart="handleTouchStart" @touchstart="handleTouchStart"
@touchmove="handleTouchMove" @touchmove="handleTouchMove"
@touchend="handleTouchEnd" @touchend="handleTouchEnd"
@ -514,10 +647,6 @@ onUnmounted(() => {
.drag-canvas { .drag-canvas {
position: absolute; position: absolute;
left: 0;
top: 800px;
width: 375px;
height: 300px;
z-index: 100; z-index: 100;
pointer-events: auto; pointer-events: auto;
} }