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