792 lines
19 KiB
Vue
792 lines
19 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="imageCanvas"
|
||
id="imageCanvas"
|
||
class="canvas-element"
|
||
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
||
@touchstart="handleTouchStart"
|
||
@touchmove="handleTouchMove"
|
||
@touchend="handleTouchEnd"
|
||
></canvas>
|
||
|
||
<view class="canvas-info">
|
||
<text>Canvas尺寸: {{ canvasWidth }} × {{ canvasHeight }} px</text>
|
||
<text>图片状态: {{ imageStatus }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="controls">
|
||
<view class="control-card">
|
||
<text class="control-title">操作说明</text>
|
||
<view class="instructions">
|
||
<text>1. 点击并按住图片开始拖拽</text>
|
||
<text>2. 拖动图片到蓝色目标区域</text>
|
||
<text>3. 释放手指完成拖放</text>
|
||
</view>
|
||
|
||
<view class="image-url">
|
||
<text>图片URL:</text>
|
||
<text class="url-text">{{ imageUrl }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="control-card">
|
||
<text class="control-title">目标区域</text>
|
||
<view :class="['target-area', { '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-card">
|
||
<text class="control-title">状态信息</text>
|
||
<view class="status">
|
||
<text :class="['status-text', { 'dragging': isDragging }, { 'success': isInTarget }]">
|
||
{{ statusText }}
|
||
</text>
|
||
</view>
|
||
|
||
<view class="stats">
|
||
<view class="stat-item">
|
||
<text class="stat-value">{{ dragCount }}</text>
|
||
<text class="stat-label">拖拽次数</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-value">{{ targetHits }}</text>
|
||
<text class="stat-label">命中目标</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-value">{{ Math.round(dragDistance) }}</text>
|
||
<text class="stat-label">移动距离</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="control-card">
|
||
<text class="control-title">图片位置</text>
|
||
<view class="position-info">
|
||
<text>X坐标: {{ image.x.toFixed(0) }} px</text>
|
||
<text>Y坐标: {{ image.y.toFixed(0) }} px</text>
|
||
<text>宽度: {{ image.width }} px</text>
|
||
<text>高度: {{ image.height }} px</text>
|
||
</view>
|
||
</view>
|
||
|
||
<button class="reset-btn" @tap="resetImage">重置图片位置</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
// Canvas尺寸
|
||
canvasWidth: 350,
|
||
canvasHeight: 500,
|
||
|
||
// Canvas上下文
|
||
ctx: null,
|
||
|
||
// 图片URL
|
||
imageUrl: 'https://static.vecteezy.com/system/resources/previews/026/105/544/non_2x/example-icon-design-free-vector.jpg',
|
||
|
||
// 图片对象
|
||
image: {
|
||
x: 50,
|
||
y: 50,
|
||
width: 120,
|
||
height: 120,
|
||
isDragging: false,
|
||
dragOffsetX: 0,
|
||
dragOffsetY: 0,
|
||
startX: 0,
|
||
startY: 0,
|
||
img: null,
|
||
loaded: false
|
||
},
|
||
|
||
// 目标区域
|
||
target: {
|
||
x: 150,
|
||
y: 200,
|
||
width: 140,
|
||
height: 100
|
||
},
|
||
|
||
// Canvas位置信息
|
||
canvasRect: {
|
||
left: 0,
|
||
top: 0,
|
||
width: 0,
|
||
height: 0
|
||
},
|
||
|
||
// 状态
|
||
isDragging: false,
|
||
isInTarget: false,
|
||
dragCount: 0,
|
||
targetHits: 0,
|
||
dragDistance: 0,
|
||
statusText: '点击图片开始拖拽',
|
||
imageStatus: '加载中...',
|
||
|
||
// 动画帧ID
|
||
animationFrameId: null,
|
||
lastRenderTime: 0,
|
||
renderInterval: 16 // ~60fps
|
||
}
|
||
},
|
||
|
||
onLoad() {
|
||
// 页面加载后初始化Canvas
|
||
this.$nextTick(() => {
|
||
setTimeout(() => {
|
||
this.initCanvas()
|
||
}, 100)
|
||
})
|
||
},
|
||
|
||
onUnload() {
|
||
// 页面卸载时停止动画
|
||
if (this.animationFrameId) {
|
||
cancelAnimationFrame(this.animationFrameId)
|
||
}
|
||
},
|
||
|
||
onShow() {
|
||
// 页面显示时开始动画
|
||
this.startAnimation()
|
||
},
|
||
|
||
onHide() {
|
||
// 页面隐藏时停止动画
|
||
if (this.animationFrameId) {
|
||
cancelAnimationFrame(this.animationFrameId)
|
||
this.animationFrameId = null
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
// 初始化Canvas
|
||
initCanvas() {
|
||
// 创建Canvas上下文
|
||
this.ctx = uni.createCanvasContext('imageCanvas', this)
|
||
|
||
// 获取Canvas位置信息
|
||
this.getCanvasPosition()
|
||
|
||
// 加载图片
|
||
this.loadImage()
|
||
|
||
// 开始动画循环
|
||
this.startAnimation()
|
||
},
|
||
|
||
// 获取Canvas位置信息
|
||
getCanvasPosition() {
|
||
const query = uni.createSelectorQuery().in(this)
|
||
query.select('#imageCanvas').boundingClientRect(res => {
|
||
if (res) {
|
||
this.canvasRect = {
|
||
left: res.left,
|
||
top: res.top,
|
||
width: res.width,
|
||
height: res.height
|
||
}
|
||
}
|
||
}).exec()
|
||
},
|
||
|
||
// 加载图片
|
||
loadImage() {
|
||
const img = new Image()
|
||
img.onload = () => {
|
||
console.log('图片加载成功')
|
||
this.image.img = img
|
||
this.image.loaded = true
|
||
this.imageStatus = '已加载'
|
||
this.statusText = '点击图片开始拖拽'
|
||
|
||
// 如果图片尺寸太大,调整显示尺寸
|
||
const maxWidth = 150
|
||
const maxHeight = 150
|
||
if (img.width > maxWidth || img.height > maxHeight) {
|
||
const ratio = Math.min(maxWidth / img.width, maxHeight / img.height)
|
||
this.image.width = img.width * ratio
|
||
this.image.height = img.height * ratio
|
||
}
|
||
}
|
||
|
||
img.onerror = () => {
|
||
console.error('图片加载失败,使用备选方案')
|
||
this.imageStatus = '加载失败,使用备选图片'
|
||
this.createFallbackImage()
|
||
}
|
||
this.createFallbackImage()
|
||
// img.src = '/static/dzm/img_duck.png'
|
||
},
|
||
|
||
// 创建备选图片(如果网络图片加载失败)
|
||
createFallbackImage() {
|
||
// 创建一个备用的Canvas来绘制图片
|
||
const fallbackCanvas = document.createElement('canvas')
|
||
const fallbackCtx = fallbackCanvas.getContext('2d')
|
||
|
||
fallbackCanvas.width = this.image.width
|
||
fallbackCanvas.height = this.image.height
|
||
|
||
// 绘制橙色背景
|
||
fallbackCtx.fillStyle = '#FF8C00'
|
||
fallbackCtx.fillRect(0, 0, this.image.width, this.image.height)
|
||
|
||
// 绘制边框
|
||
fallbackCtx.strokeStyle = '#333'
|
||
fallbackCtx.lineWidth = 2
|
||
fallbackCtx.strokeRect(0, 0, this.image.width, this.image.height)
|
||
|
||
// 绘制示例图标
|
||
fallbackCtx.fillStyle = 'white'
|
||
fallbackCtx.font = 'bold 14px Arial'
|
||
fallbackCtx.textAlign = 'center'
|
||
fallbackCtx.textBaseline = 'middle'
|
||
fallbackCtx.fillText('示例', this.image.width / 2, this.image.height / 2 - 10)
|
||
|
||
fallbackCtx.font = '12px Arial'
|
||
fallbackCtx.fillText('图片', this.image.width / 2, this.image.height / 2 + 10)
|
||
|
||
// 转换为图片
|
||
const img = new Image()
|
||
img.src = fallbackCanvas.toDataURL('image/png')
|
||
img.onload = () => {
|
||
this.image.img = img
|
||
this.image.loaded = true
|
||
}
|
||
},
|
||
|
||
// 开始动画循环
|
||
startAnimation() {
|
||
const animate = (timestamp) => {
|
||
if (timestamp - this.lastRenderTime >= this.renderInterval) {
|
||
this.drawCanvas()
|
||
this.lastRenderTime = timestamp
|
||
}
|
||
this.animationFrameId = requestAnimationFrame(animate)
|
||
}
|
||
this.animationFrameId = requestAnimationFrame(animate)
|
||
},
|
||
|
||
// 绘制Canvas内容
|
||
drawCanvas() {
|
||
if (!this.ctx) return
|
||
|
||
// 清除Canvas
|
||
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
|
||
|
||
// 绘制目标区域
|
||
this.drawTargetArea()
|
||
|
||
// 绘制可拖拽图片
|
||
this.drawImage()
|
||
|
||
// 绘制坐标信息
|
||
this.drawInfo()
|
||
|
||
// 执行绘制
|
||
this.ctx.draw(true)
|
||
},
|
||
|
||
// 绘制目标区域
|
||
drawTargetArea() {
|
||
this.ctx.setFillStyle(this.isInTarget ? 'rgba(46, 204, 113, 0.2)' : 'rgba(52, 152, 219, 0.1)')
|
||
this.ctx.setStrokeStyle(this.isInTarget ? '#2ecc71' : '#3498db')
|
||
this.ctx.setLineWidth(2)
|
||
this.ctx.setLineDash([5, 5])
|
||
|
||
this.ctx.fillRect(this.target.x, this.target.y, this.target.width, this.target.height)
|
||
this.ctx.strokeRect(this.target.x, this.target.y, this.target.width, this.target.height)
|
||
this.ctx.setLineDash([])
|
||
|
||
// 绘制目标区域文字
|
||
this.ctx.setFontSize(12)
|
||
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
|
||
)
|
||
},
|
||
|
||
// 绘制图片
|
||
drawImage() {
|
||
if (this.image.loaded && this.image.img) {
|
||
// 绘制图片
|
||
this.ctx.drawImage(
|
||
this.image.img,
|
||
this.image.x,
|
||
this.image.y,
|
||
this.image.width,
|
||
this.image.height
|
||
)
|
||
|
||
// 如果正在拖拽,绘制高亮边框
|
||
if (this.image.isDragging) {
|
||
this.ctx.setStrokeStyle('#e74c3c')
|
||
this.ctx.setLineWidth(3)
|
||
this.ctx.setLineDash([5, 5])
|
||
this.ctx.strokeRect(
|
||
this.image.x - 5,
|
||
this.image.y - 5,
|
||
this.image.width + 10,
|
||
this.image.height + 10
|
||
)
|
||
this.ctx.setLineDash([])
|
||
}
|
||
} else {
|
||
// 图片未加载时绘制占位符
|
||
this.ctx.setFillStyle('#FF8C00')
|
||
this.ctx.fillRect(this.image.x, this.image.y, this.image.width, this.image.height)
|
||
|
||
this.ctx.setStrokeStyle('#333')
|
||
this.ctx.setLineWidth(2)
|
||
this.ctx.strokeRect(this.image.x, this.image.y, this.image.width, this.image.height)
|
||
|
||
this.ctx.setFontSize(14)
|
||
this.ctx.setFillStyle('#FFFFFF')
|
||
this.ctx.setTextAlign('center')
|
||
this.ctx.fillText(
|
||
'加载中...',
|
||
this.image.x + this.image.width / 2,
|
||
this.image.y + this.image.height / 2
|
||
)
|
||
}
|
||
},
|
||
|
||
// 绘制信息
|
||
drawInfo() {
|
||
this.ctx.setFontSize(10)
|
||
this.ctx.setFillStyle('#666')
|
||
this.ctx.setTextAlign('left')
|
||
this.ctx.setTextBaseline('top')
|
||
|
||
// 图片位置信息
|
||
this.ctx.fillText(
|
||
`图片位置: (${Math.round(this.image.x)}, ${Math.round(this.image.y)})`,
|
||
10, 10
|
||
)
|
||
|
||
// 目标位置信息
|
||
this.ctx.fillText(
|
||
`目标位置: (${this.target.x}, ${this.target.y})`,
|
||
10, 25
|
||
)
|
||
|
||
// 拖拽次数信息
|
||
this.ctx.fillText(
|
||
`拖拽次数: ${this.dragCount}`,
|
||
10, 40
|
||
)
|
||
},
|
||
|
||
// 检查触摸点是否在图片内
|
||
isPointInImage(x, y) {
|
||
return x >= this.image.x &&
|
||
x <= this.image.x + this.image.width &&
|
||
y >= this.image.y &&
|
||
y <= this.image.y + this.image.height
|
||
},
|
||
|
||
// 检查图片是否在目标区域内
|
||
checkImageInTarget() {
|
||
const img = this.image
|
||
const imgCenterX = img.x + img.width / 2
|
||
const imgCenterY = img.y + img.height / 2
|
||
|
||
const hit =
|
||
imgCenterX >= this.target.x &&
|
||
imgCenterX <= this.target.x + this.target.width &&
|
||
imgCenterY >= this.target.y &&
|
||
imgCenterY <= this.target.y + this.target.height
|
||
|
||
this.isInTarget = hit
|
||
return hit
|
||
},
|
||
|
||
// 计算两点距离
|
||
calculateDistance(x1, y1, x2, y2) {
|
||
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
|
||
},
|
||
|
||
// 触摸开始事件
|
||
handleTouchStart(e) {
|
||
const touch = e.touches[0]
|
||
if (!touch) return
|
||
|
||
// 更新Canvas位置信息
|
||
this.getCanvasPosition()
|
||
|
||
// 异步执行,确保Canvas位置信息已更新
|
||
setTimeout(() => {
|
||
const touchX = touch.clientX - this.canvasRect.left
|
||
const touchY = touch.clientY - this.canvasRect.top
|
||
|
||
if (this.isPointInImage(touchX, touchY)) {
|
||
this.image.isDragging = true
|
||
this.image.dragOffsetX = touchX - this.image.x
|
||
this.image.dragOffsetY = touchY - this.image.y
|
||
this.image.startX = this.image.x
|
||
this.image.startY = this.image.y
|
||
|
||
this.isDragging = true
|
||
this.statusText = '正在拖拽图片...'
|
||
}
|
||
}, 50)
|
||
},
|
||
|
||
// 触摸移动事件
|
||
handleTouchMove(e) {
|
||
if (!this.image.isDragging) return
|
||
|
||
e.preventDefault()
|
||
|
||
const touch = e.touches[0]
|
||
if (!touch) return
|
||
|
||
const touchX = touch.clientX - this.canvasRect.left
|
||
const touchY = touch.clientY - this.canvasRect.top
|
||
|
||
// 计算移动距离
|
||
const distance = this.calculateDistance(
|
||
this.image.x,
|
||
this.image.y,
|
||
touchX - this.image.dragOffsetX,
|
||
touchY - this.image.dragOffsetY
|
||
)
|
||
|
||
this.dragDistance += distance
|
||
|
||
// 更新图片位置
|
||
this.image.x = touchX - this.image.dragOffsetX
|
||
this.image.y = touchY - this.image.dragOffsetY
|
||
|
||
// 限制边界
|
||
this.image.x = Math.max(0, Math.min(
|
||
this.canvasWidth - this.image.width,
|
||
this.image.x
|
||
))
|
||
|
||
this.image.y = Math.max(0, Math.min(
|
||
this.canvasHeight - this.image.height,
|
||
this.image.y
|
||
))
|
||
|
||
// 检查是否进入目标区域
|
||
this.checkImageInTarget()
|
||
},
|
||
|
||
// 触摸结束事件
|
||
handleTouchEnd() {
|
||
if (this.image.isDragging) {
|
||
this.image.isDragging = false
|
||
this.isDragging = false
|
||
this.dragCount++
|
||
|
||
if (this.checkImageInTarget()) {
|
||
this.targetHits++
|
||
this.statusText = '图片已在目标区域内'
|
||
} else {
|
||
this.statusText = '拖拽完成'
|
||
}
|
||
}
|
||
},
|
||
|
||
// 重置图片位置
|
||
resetImage() {
|
||
this.image.x = 50
|
||
this.image.y = 50
|
||
this.image.isDragging = false
|
||
this.isDragging = false
|
||
this.isInTarget = false
|
||
this.statusText = '位置已重置,点击图片开始拖拽'
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.canvas-container {
|
||
padding: 20px;
|
||
background-color: #f5f7fa;
|
||
min-height: 100vh;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
|
||
}
|
||
|
||
.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: 22px;
|
||
font-weight: 700;
|
||
color: #2c3e50;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.subtitle {
|
||
display: block;
|
||
font-size: 16px;
|
||
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-info {
|
||
margin-top: 10px;
|
||
padding: 10px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 8px;
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.canvas-info text {
|
||
font-size: 12px;
|
||
color: #666;
|
||
text-align: center;
|
||
}
|
||
|
||
.controls {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.control-card {
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
padding: 15px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.control-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: 8px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.instructions text {
|
||
display: block;
|
||
color: #555;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.image-url {
|
||
background-color: #f8f9fa;
|
||
padding: 10px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
}
|
||
|
||
.image-url text {
|
||
font-size: 12px;
|
||
color: #666;
|
||
}
|
||
|
||
.url-text {
|
||
word-break: break-all;
|
||
color: #3498db !important;
|
||
font-family: monospace;
|
||
font-size: 11px !important;
|
||
}
|
||
|
||
.target-area {
|
||
margin-top: 10px;
|
||
padding: 12px;
|
||
border: 2px dashed #3498db;
|
||
border-radius: 8px;
|
||
background-color: #e8f4fc;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 5px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.target-area text {
|
||
display: block;
|
||
color: #3498db;
|
||
font-size: 13px;
|
||
text-align: center;
|
||
}
|
||
|
||
.target-hit {
|
||
background-color: #d1f2eb;
|
||
border-color: #2ecc71;
|
||
border-style: solid;
|
||
}
|
||
|
||
.success-text {
|
||
color: #27ae60 !important;
|
||
font-weight: bold;
|
||
margin-top: 5px !important;
|
||
}
|
||
|
||
.status {
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
background-color: #f8f9fa;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.status-text {
|
||
display: block;
|
||
text-align: center;
|
||
font-weight: 500;
|
||
color: #666;
|
||
}
|
||
|
||
.status-text.dragging {
|
||
color: #e67e22;
|
||
background-color: #fff9e6;
|
||
padding: 8px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.status-text.success {
|
||
color: #27ae60;
|
||
background-color: #d4f8e8;
|
||
padding: 8px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.stats {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
background-color: #f8f9fa;
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #3498db;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 11px;
|
||
color: #7f8c8d;
|
||
}
|
||
|
||
.position-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
background-color: #f8f9fa;
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.position-info text {
|
||
display: block;
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
|
||
.reset-btn {
|
||
background-color: #3498db;
|
||
color: white;
|
||
border: none;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
transition: all 0.3s;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.reset-btn:active {
|
||
background-color: #2980b9;
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
.main-content {
|
||
flex-direction: row;
|
||
}
|
||
|
||
.canvas-wrapper {
|
||
flex: 1;
|
||
}
|
||
|
||
.controls {
|
||
flex: 1;
|
||
min-width: 300px;
|
||
}
|
||
}
|
||
</style>
|