qs_xinchun2026_h5/pages/canvas3.vue

830 lines
20 KiB
Vue
Raw Permalink 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.

<template>
<view class="canvas-container">
<view class="header">
<text class="title">Canvas拖拽功能完全修复版</text>
<text class="subtitle">已修复方块不跟随拖动的根本问题</text>
</view>
<view class="main-content">
<view class="canvas-wrapper">
<canvas
canvas-id="dragCanvas"
id="dragCanvas"
class="canvas-element"
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
></canvas>
<view class="canvas-status">
<text>{{ statusText }}</text>
</view>
</view>
<view class="controls">
<view class="control-section">
<text class="section-title">操作说明</text>
<view class="instructions">
<view class="instruction-item">
<view class="step-number">1</view>
<text>点击橙色方块</text>
</view>
<view class="instruction-item">
<view class="step-number">2</view>
<text>拖动到蓝色目标区域</text>
</view>
<view class="instruction-item">
<view class="step-number">3</view>
<text>释放手指完成拖放</text>
</view>
</view>
</view>
<view class="control-section">
<text class="section-title">调试信息</text>
<view class="debug-info">
<text class="debug-item">方块位置: X={{ rect.x.toFixed(0) }}, Y={{ rect.y.toFixed(0) }}</text>
<text class="debug-item">触摸位置: X={{ touchPos.x.toFixed(0) }}, Y={{ touchPos.y.toFixed(0) }}</text>
<text class="debug-item">触摸状态: {{ isDragging ? '拖拽中' : '未拖拽' }}</text>
<text class="debug-item">事件触发: {{ lastEvent }}</text>
</view>
</view>
<view class="control-section">
<text class="section-title">目标区域</text>
<view :class="['target-display', { 'target-hit': isInTarget }]">
<text>目标: ({{ target.x }}, {{ target.y }})</text>
<text>尺寸: {{ target.width }}×{{ target.height }}</text>
<text v-if="isInTarget" class="success-text">✅ 成功放入目标!</text>
</view>
</view>
<view class="control-section">
<text class="section-title">统计数据</text>
<view class="stats">
<view class="stat-box">
<text class="stat-value">{{ dragCount }}</text>
<text class="stat-label">拖拽次数</text>
</view>
<view class="stat-box">
<text class="stat-value">{{ successCount }}</text>
<text class="stat-label">成功次数</text>
</view>
<view class="stat-box">
<text class="stat-value">{{ Math.round(dragDistance) }}</text>
<text class="stat-label">拖拽距离</text>
</view>
</view>
</view>
<view class="buttons">
<button class="btn reset-btn" @tap="resetAll">重置所有</button>
<button class="btn debug-btn" @tap="toggleDebug">调试模式: {{ debugMode ? '开启' : '关闭' }}</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
// Canvas尺寸
canvasWidth: 350,
canvasHeight: 500,
// Canvas上下文
ctx: null,
// 图片对象
duckImage: null,
// 拖拽方块
rect: {
x: 50,
y: 50,
width: 54,
height: 105,
color: '#FF8C00',
isDragging: false,
dragOffsetX: 0,
dragOffsetY: 0,
lastX: 50,
lastY: 50
},
// 目标区域
target: {
x: 150,
y: 200,
width: 130,
height: 90,
color: 'rgba(52, 152, 219, 0.3)'
},
// 触摸位置
touchPos: {
x: 0,
y: 0,
clientX: 0,
clientY: 0
},
// Canvas位置信息
canvasRect: {
left: 0,
top: 0,
width: 0,
height: 0
},
// 状态
isDragging: false,
isInTarget: false,
dragCount: 0,
successCount: 0,
dragDistance: 0,
statusText: '点击橙色方块开始拖拽',
lastEvent: '无',
debugMode: true,
// 动画帧
animationId: null,
lastRenderTime: 0,
renderInterval: 16 // ~60fps
}
},
onLoad() {
// 页面加载后初始化Canvas
this.$nextTick(() => {
setTimeout(() => {
this.initCanvas()
this.getCanvasPosition()
}, 100)
})
},
onUnload() {
// 清理动画帧
if (this.animationId) {
cancelAnimationFrame(this.animationId)
}
},
onShow() {
// 页面显示时开始绘制
this.startAnimation()
},
onHide() {
// 页面隐藏时停止绘制
if (this.animationId) {
cancelAnimationFrame(this.animationId)
this.animationId = null
}
},
methods: {
// 初始化Canvas
initCanvas() {
console.log('正在初始化Canvas...')
// 在uni-app中必须使用uni.createCanvasContext
this.ctx = uni.createCanvasContext('dragCanvas', this)
if (this.ctx) {
console.log('Canvas上下文创建成功')
this.lastEvent = 'Canvas初始化成功'
// 加载图片
this.loadDuckImage()
} else {
console.error('Canvas上下文创建失败')
this.lastEvent = 'Canvas初始化失败'
}
},
// 加载鸭子图片
loadDuckImage() {
// 使用 uni.getImageInfo 获取图片信息
uni.getImageInfo({
src: '/static/dzm/img_duck.png',
success: (res) => {
console.log('鸭子图片加载成功:', res)
this.duckImage = res.path
this.lastEvent = '图片加载成功'
},
fail: (err) => {
console.error('鸭子图片加载失败:', err)
this.lastEvent = '图片加载失败'
}
})
},
// 获取Canvas元素位置 - 关键修复函数
getCanvasPosition() {
const query = uni.createSelectorQuery().in(this)
query.select('#dragCanvas').boundingClientRect(res => {
if (res) {
this.canvasRect = {
left: res.left,
top: res.top,
width: res.width,
height: res.height
}
console.log('Canvas位置信息:', this.canvasRect)
this.lastEvent = `Canvas位置: left=${res.left.toFixed(1)}, top=${res.top.toFixed(1)}`
} else {
console.error('无法获取Canvas位置信息')
this.lastEvent = '无法获取Canvas位置'
}
}).exec()
},
// 开始动画循环
startAnimation() {
const animate = (timestamp) => {
// 控制渲染频率
if (timestamp - this.lastRenderTime >= this.renderInterval) {
this.drawCanvas()
this.lastRenderTime = timestamp
}
this.animationId = requestAnimationFrame(animate)
}
this.animationId = requestAnimationFrame(animate)
},
// 绘制Canvas
drawCanvas() {
if (!this.ctx) return
// 清除Canvas
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
// 绘制目标区域
this.drawTarget()
// 绘制可拖拽方块
this.drawDraggableRect()
// 绘制状态信息
this.drawDebugInfo()
// 执行绘制
this.ctx.draw(true) // 设置为true使用延迟绘制
},
// 绘制目标区域
drawTarget() {
// 绘制目标区域背景
this.ctx.setFillStyle(this.target.color)
this.ctx.fillRect(this.target.x, this.target.y, this.target.width, this.target.height)
// 绘制目标区域边框
this.ctx.setStrokeStyle(this.isInTarget ? '#2ecc71' : '#3498db')
this.ctx.setLineWidth(2)
this.ctx.setLineDash([5, 5])
this.ctx.strokeRect(this.target.x, this.target.y, this.target.width, this.target.height)
this.ctx.setLineDash([])
// 绘制目标区域文字
this.ctx.setFontSize(14)
this.ctx.setFillStyle('#3498db')
this.ctx.setTextAlign('center')
this.ctx.fillText(
'目标区域',
this.target.x + this.target.width / 2,
this.target.y + this.target.height / 2
)
},
// 绘制可拖拽方块
drawDraggableRect() {
const rect = this.rect
// 如果图片已加载,绘制图片
if (this.duckImage) {
this.ctx.drawImage(this.duckImage, rect.x, rect.y, rect.width, rect.height)
} else {
// 图片未加载时,绘制占位方块
this.ctx.setFillStyle(rect.color)
this.ctx.fillRect(rect.x, rect.y, rect.width, rect.height)
// 绘制方块边框
this.ctx.setStrokeStyle('#333')
this.ctx.setLineWidth(2)
this.ctx.strokeRect(rect.x, rect.y, rect.width, rect.height)
// 绘制"拖我"文字
this.ctx.setFontSize(20)
this.ctx.setFillStyle('#FFFFFF')
this.ctx.setTextAlign('center')
this.ctx.setTextBaseline('middle')
this.ctx.fillText(
'拖我',
rect.x + rect.width / 2,
rect.y + rect.height / 2
)
}
// 如果正在拖拽,绘制拖拽效果
if (rect.isDragging) {
// 绘制拖拽阴影
this.ctx.setFillStyle('rgba(231, 76, 60, 0.3)')
this.ctx.fillRect(rect.x, rect.y, rect.width, rect.height)
// 绘制拖拽边框
this.ctx.setStrokeStyle('#e74c3c')
this.ctx.setLineWidth(3)
this.ctx.setLineDash([5, 5])
this.ctx.strokeRect(rect.x - 5, rect.y - 5, rect.width + 10, rect.height + 10)
this.ctx.setLineDash([])
}
},
// 绘制调试信息
drawDebugInfo() {
if (!this.debugMode) return
this.ctx.setFontSize(12)
this.ctx.setFillStyle('#666666')
this.ctx.setTextAlign('left')
// 方块位置
this.ctx.fillText(
`方块: (${Math.round(this.rect.x)}, ${Math.round(this.rect.y)})`,
10, 20
)
// 触摸位置
this.ctx.fillText(
`触摸: (${Math.round(this.touchPos.x)}, ${Math.round(this.touchPos.y)})`,
10, 40
)
// 状态信息
this.ctx.fillText(
`状态: ${this.isDragging ? '拖拽中' : '静止'}`,
10, 60
)
// 拖拽次数
this.ctx.fillText(
`次数: ${this.dragCount}`,
10, 80
)
},
// 检查触摸点是否在方块内
checkTouchInRect(x, y) {
const rect = this.rect
return x >= rect.x &&
x <= rect.x + rect.width &&
y >= rect.y &&
y <= rect.y + rect.height
},
// 检查方块是否在目标区域内
checkRectInTarget() {
const rect = this.rect
const target = this.target
// 计算方块中心点
const rectCenterX = rect.x + rect.width / 2
const rectCenterY = rect.y + rect.height / 2
// 检查中心点是否在目标区域内
const isInTarget =
rectCenterX >= target.x &&
rectCenterX <= target.x + target.width &&
rectCenterY >= target.y &&
rectCenterY <= target.y + target.height
this.isInTarget = isInTarget
return isInTarget
},
// 计算两点距离
calculateDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
},
// 触摸开始事件
handleTouchStart(e) {
console.log('触摸开始事件触发', e)
this.lastEvent = 'touchstart'
// 获取触摸点
const touch = e.touches[0]
if (!touch) {
console.error('没有触摸点信息')
return
}
// 确保Canvas位置信息是最新的
this.getCanvasPosition()
// 异步获取Canvas位置后执行检查
setTimeout(() => {
// 计算相对于Canvas的坐标
const touchX = touch.clientX - this.canvasRect.left
const touchY = touch.clientY - this.canvasRect.top
console.log(`触摸点 - 客户端: (${touch.clientX}, ${touch.clientY})`)
console.log(`Canvas位置: left=${this.canvasRect.left}, top=${this.canvasRect.top}`)
console.log(`触摸点相对Canvas: (${touchX}, ${touchY})`)
// 更新触摸位置
this.touchPos = {
x: touchX,
y: touchY,
clientX: touch.clientX,
clientY: touch.clientY
}
// 检查是否点击在方块上
if (this.checkTouchInRect(touchX, touchY)) {
console.log('触摸点在方块内,开始拖拽')
// 设置拖拽状态和偏移量
this.rect.isDragging = true
this.rect.dragOffsetX = touchX - this.rect.x
this.rect.dragOffsetY = touchY - this.rect.y
this.rect.lastX = this.rect.x
this.rect.lastY = this.rect.y
this.isDragging = true
this.statusText = '拖拽中...'
console.log(`拖拽偏移量: offsetX=${this.rect.dragOffsetX}, offsetY=${this.rect.dragOffsetY}`)
} else {
console.log('触摸点不在方块内')
this.statusText = '请点击橙色方块'
}
}, 50)
},
// 触摸移动事件 - 关键修复函数
handleTouchMove(e) {
// 只有开始拖拽后才处理移动事件
if (!this.rect.isDragging) {
return
}
e.preventDefault()
e.stopPropagation()
this.lastEvent = 'touchmove'
const touch = e.touches[0]
if (!touch) return
// 计算相对于Canvas的坐标
const touchX = touch.clientX - this.canvasRect.left
const touchY = touch.clientY - this.canvasRect.top
// 更新触摸位置
this.touchPos = {
x: touchX,
y: touchY,
clientX: touch.clientX,
clientY: touch.clientY
}
// 计算移动距离
const distance = this.calculateDistance(
this.rect.x,
this.rect.y,
touchX - this.rect.dragOffsetX,
touchY - this.rect.dragOffsetY
)
this.dragDistance += distance
// 关键修复:直接更新方块位置
const newX = touchX - this.rect.dragOffsetX
const newY = touchY - this.rect.dragOffsetY
console.log(`触摸移动: 新位置(${newX}, ${newY}),旧位置(${this.rect.x}, ${this.rect.y})`)
// 更新方块位置
this.rect.x = newX
this.rect.y = newY
// 限制边界
this.rect.x = Math.max(0, Math.min(
this.canvasWidth - this.rect.width,
this.rect.x
))
this.rect.y = Math.max(0, Math.min(
this.canvasHeight - this.rect.height,
this.rect.y
))
// 检查是否进入目标区域
this.checkRectInTarget()
// 强制立即重绘 - 关键修复
this.forceRedraw()
},
// 强制立即重绘
forceRedraw() {
if (this.ctx) {
// 直接调用绘制函数,不等待下一帧
this.drawCanvas()
}
},
// 触摸结束事件
handleTouchEnd() {
console.log('触摸结束事件触发')
this.lastEvent = 'touchend'
if (this.rect.isDragging) {
this.rect.isDragging = false
this.isDragging = false
// 增加拖拽次数
this.dragCount++
// 检查是否成功放入目标区域
if (this.checkRectInTarget()) {
this.successCount++
this.statusText = '成功放入目标区域!'
} else {
this.statusText = '拖拽完成,未放入目标区域'
}
console.log(`拖拽结束,总次数: ${this.dragCount}`)
// 强制重绘以移除拖拽效果
this.forceRedraw()
}
},
// 重置所有状态
resetAll() {
this.rect.x = 50
this.rect.y = 50
this.rect.isDragging = false
this.isDragging = false
this.isInTarget = false
this.dragCount = 0
this.successCount = 0
this.dragDistance = 0
this.statusText = '已重置,点击橙色方块开始拖拽'
this.lastEvent = '重置'
// 重新获取Canvas位置
this.getCanvasPosition()
// 强制重绘
this.forceRedraw()
},
// 切换调试模式
toggleDebug() {
this.debugMode = !this.debugMode
this.forceRedraw()
}
}
}
</script>
<style>
.canvas-container {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.header {
text-align: center;
margin-bottom: 20px;
padding: 20px;
background-color: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.title {
display: block;
font-size: 20px;
font-weight: 700;
color: #2c3e50;
margin-bottom: 8px;
}
.subtitle {
display: block;
font-size: 14px;
color: #7f8c8d;
}
.main-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.canvas-wrapper {
background-color: white;
border-radius: 12px;
padding: 15px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
}
.canvas-element {
border-radius: 8px;
background-color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
touch-action: none;
}
.canvas-status {
margin-top: 10px;
padding: 10px 15px;
background-color: #f8f9fa;
border-radius: 8px;
width: 100%;
text-align: center;
}
.canvas-status text {
font-size: 14px;
color: #495057;
font-weight: 500;
}
.controls {
display: flex;
flex-direction: column;
gap: 15px;
}
.control-section {
background-color: white;
border-radius: 12px;
padding: 15px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 12px;
display: block;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
}
.instructions {
display: flex;
flex-direction: column;
gap: 10px;
}
.instruction-item {
display: flex;
align-items: center;
gap: 10px;
}
.step-number {
width: 24px;
height: 24px;
background-color: #3498db;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
}
.debug-info {
display: flex;
flex-direction: column;
gap: 6px;
background-color: #f8f9fa;
padding: 12px;
border-radius: 8px;
}
.debug-item {
font-size: 12px;
color: #666;
font-family: monospace;
}
.target-display {
background-color: #e8f4fc;
border: 2px dashed #3498db;
border-radius: 8px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 6px;
transition: all 0.3s;
}
.target-display.target-hit {
background-color: #d1f2eb;
border-color: #2ecc71;
border-style: solid;
}
.target-display text {
font-size: 13px;
color: #3498db;
}
.success-text {
color: #27ae60 !important;
font-weight: 600;
margin-top: 5px;
}
.stats {
display: flex;
justify-content: space-between;
gap: 10px;
}
.stat-box {
flex: 1;
background-color: #f8f9fa;
border-radius: 8px;
padding: 12px;
display: flex;
flex-direction: column;
align-items: center;
}
.stat-value {
font-size: 20px;
font-weight: 700;
color: #3498db;
margin-bottom: 4px;
}
.stat-label {
font-size: 11px;
color: #7f8c8d;
}
.buttons {
display: flex;
gap: 10px;
}
.btn {
flex: 1;
padding: 12px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
text-align: center;
border: none;
transition: all 0.2s;
}
.btn:active {
transform: scale(0.98);
}
.reset-btn {
background-color: #3498db;
color: white;
}
.debug-btn {
background-color: #6c757d;
color: white;
}
@media (min-width: 768px) {
.main-content {
flex-direction: row;
}
.canvas-wrapper {
flex: 1;
}
.controls {
flex: 1;
min-width: 300px;
}
}
</style>