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 showGuideElements = ref(true)
// Canvas 触摸是否禁用
const canvasDisabled = ref(false)
// 调试模式
const debugMode = ref(false)
// Canvas 上下文
const ctx = ref(null)
// 鸭子图片路径
const duckImagePath = ref(null)
// 鸭子位置(相对于 Canvas)
const duckX = ref(275)
const duckY = ref(110)
// rpx 转换比例
const rpxRatio = ref(0.5) // 默认 iPhone 6/7/8 的 375px / 750rpx = 0.5
// 鸭子尺寸(与图片实际尺寸一致)
const duckWidth = 36
const duckHeight = 70
// 鸭子位置和尺寸(使用 rpx 单位,在 750rpx 设计稿下)
const duckRpx = ref({
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 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({
left: 0,
top: 0,
@ -70,12 +93,20 @@ const canvasRect = ref({
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({
x: 0,
y: 80,
width: 200,
height: 150
y: 0,
width: 0,
height: 0
})
// 动画帧 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 元素位置
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)
query.select('#dragDuckCanvas').boundingClientRect(res => {
if (res) {
canvasRect.value = {
canvasBoundingRect.value = {
left: res.left,
top: res.top,
width: res.width,
height: res.height
top: res.top
}
console.log('Canvas 位置信息:', canvasRect.value)
} else {
console.error('无法获取 Canvas 位置信息')
console.log('Canvas 视口位置:', canvasBoundingRect.value)
}
}).exec()
}
@ -163,6 +244,8 @@ const drawCanvas = () => {
const drawTarget = () => {
const target = targetArea.value
if (debugMode.value) {
// 调试模式:显示目标区域边框和文字
// 绘制目标区域背景
ctx.value.setFillStyle('rgba(52, 152, 219, 0.3)')
ctx.value.fillRect(target.x, target.y, target.width, target.height)
@ -184,43 +267,60 @@ const drawTarget = () => {
target.y + target.height / 2
)
}
// 非调试模式:完全透明,不绘制任何内容
}
// 绘制鸭子
const drawDuck = () => {
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) {
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 {
// 图片未加载时,绘制占位方块
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.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.setFillStyle('#FFFFFF')
ctx.value.setTextAlign('center')
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.fillRect(duckX.value, duckY.value, duckWidth, duckHeight)
ctx.value.fillRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value)
// 绘制拖拽边框
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.strokeRect(duckX.value - 3, duckY.value - 3, duckWidth.value + 6, duckHeight.value + 6)
ctx.value.setLineDash([])
}
}
@ -228,9 +328,9 @@ const drawDuck = () => {
// 检查触摸点是否在鸭子内
const checkTouchInDuck = (x, y) => {
return x >= duckX.value &&
x <= duckX.value + duckWidth &&
x <= duckX.value + duckWidth.value &&
y >= duckY.value &&
y <= duckY.value + duckHeight
y <= duckY.value + duckHeight.value
}
// 检查鸭子是否在目标区域内
@ -238,8 +338,8 @@ const checkDuckInTarget = () => {
const target = targetArea.value
// 计算鸭子中心点
const duckCenterX = duckX.value + duckWidth / 2
const duckCenterY = duckY.value + duckHeight / 2
const duckCenterX = duckX.value + duckWidth.value / 2
const duckCenterY = duckY.value + duckHeight.value / 2
// 检查中心点是否在目标区域内
return duckCenterX >= target.x &&
@ -250,6 +350,12 @@ const checkDuckInTarget = () => {
// 触摸开始事件
const handleTouchStart = (e) => {
// 如果 Canvas 已禁用,不处理触摸事件
if (canvasDisabled.value) {
console.log('Canvas 已禁用,忽略触摸事件')
return
}
console.log('触摸开始事件触发', e)
// 获取触摸点
@ -259,17 +365,20 @@ const handleTouchStart = (e) => {
return
}
// 确保 Canvas 位置信息是最新的
getCanvasPosition()
// 获取 Canvas 在视口中的实际位置
getCanvasBoundingRect()
// 异步获取 Canvas 位置后执行检查
setTimeout(() => {
// 计算相对于 Canvas 的坐标
const touchX = touch.clientX - canvasRect.value.left
const touchY = touch.clientY - canvasRect.value.top
// 计算相对于 Canvas 的坐标(使用视口位置)
const touchX = touch.clientX - canvasBoundingRect.value.left
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(`鸭子位置: (${duckX.value}, ${duckY.value})`)
console.log(`鸭子尺寸: (${duckWidth.value}, ${duckHeight.value})`)
// 检查是否点击在鸭子上
if (checkTouchInDuck(touchX, touchY)) {
@ -296,9 +405,9 @@ const handleTouchMove = (e) => {
const touch = e.touches[0]
if (!touch) return
// 计算相对于 Canvas 的坐标
const touchX = touch.clientX - canvasRect.value.left
const touchY = touch.clientY - canvasRect.value.top
// 计算相对于 Canvas 的坐标(使用视口位置)
const touchX = touch.clientX - canvasBoundingRect.value.left
const touchY = touch.clientY - canvasBoundingRect.value.top
// 更新鸭子位置
duckX.value = touchX - dragOffsetX.value
@ -308,8 +417,8 @@ const handleTouchMove = (e) => {
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))
duckX.value = Math.max(0, Math.min(canvasW - duckWidth.value, duckX.value))
duckY.value = Math.max(0, Math.min(canvasH - duckHeight.value, duckY.value))
console.log(`鸭子新位置: (${duckX.value}, ${duckY.value})`)
}
@ -330,10 +439,27 @@ const handleTouchEnd = () => {
showGuideElements.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) => {
console.log('开始拖拽', e)
@ -343,9 +469,10 @@ const startDrag = (e) => {
initCanvas()
}
// 设置鸭子初始位置(在触发区域附近)
duckX.value = 50
duckY.value = 50
// 设置鸭子初始位置(使用 rpx 值,会自动转换为 px)
duckRpx.value.x = 100 // 100rpx 对应约 50px (iPhone 6/7/8)
duckRpx.value.y = 100
updateDuckPosition()
showDuck.value = true
// 触发触摸开始事件处理
@ -413,6 +540,12 @@ onUnmounted(() => {
canvas-id="dragDuckCanvas"
id="dragDuckCanvas"
class="drag-canvas"
:style="{
left: canvasRect.left + 'px',
top: canvasRect.top + 'px',
width: canvasRect.width + 'px',
height: canvasRect.height + 'px'
}"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
@ -514,10 +647,6 @@ onUnmounted(() => {
.drag-canvas {
position: absolute;
left: 0;
top: 800px;
width: 375px;
height: 300px;
z-index: 100;
pointer-events: auto;
}