580 lines
19 KiB
HTML
580 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Canvas图片拖放功能</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
max-width: 800px;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 2.5rem;
|
||
color: #2c3e50;
|
||
margin-bottom: 10px;
|
||
text-shadow: 1px 1px 3px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 1.2rem;
|
||
color: #7f8c8d;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.container {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: 30px;
|
||
max-width: 1200px;
|
||
width: 100%;
|
||
}
|
||
|
||
.canvas-container {
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||
padding: 20px;
|
||
flex: 1;
|
||
min-width: 300px;
|
||
max-width: 700px;
|
||
}
|
||
|
||
canvas {
|
||
display: block;
|
||
border-radius: 8px;
|
||
background-color: white;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.controls {
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||
padding: 25px;
|
||
flex: 0 0 300px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
|
||
.control-group {
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
h2 {
|
||
font-size: 1.5rem;
|
||
color: #2c3e50;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 2px solid #eaeaea;
|
||
}
|
||
|
||
h3 {
|
||
font-size: 1.2rem;
|
||
color: #3498db;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.instructions {
|
||
line-height: 1.6;
|
||
color: #555;
|
||
}
|
||
|
||
.instructions li {
|
||
margin-bottom: 8px;
|
||
padding-left: 5px;
|
||
}
|
||
|
||
.target-area {
|
||
background-color: #e8f4fc;
|
||
border: 2px dashed #3498db;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
text-align: center;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.target-area.active {
|
||
background-color: #d1f2eb;
|
||
border-color: #2ecc71;
|
||
}
|
||
|
||
.status {
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
background-color: #f9f9f9;
|
||
text-align: center;
|
||
font-weight: 500;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.status.dragging {
|
||
background-color: #fff9e6;
|
||
color: #e67e22;
|
||
}
|
||
|
||
.status.success {
|
||
background-color: #d4f8e8;
|
||
color: #27ae60;
|
||
}
|
||
|
||
.reset-btn {
|
||
background-color: #3498db;
|
||
color: white;
|
||
border: none;
|
||
padding: 14px 20px;
|
||
border-radius: 8px;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
width: 100%;
|
||
}
|
||
|
||
.reset-btn:hover {
|
||
background-color: #2980b9;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(41, 128, 185, 0.2);
|
||
}
|
||
|
||
.character-info {
|
||
background-color: #fff8e1;
|
||
border-left: 4px solid #ffb300;
|
||
padding: 12px 15px;
|
||
border-radius: 0 8px 8px 0;
|
||
}
|
||
|
||
.stats {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
background-color: #f8f9fa;
|
||
padding: 10px 15px;
|
||
border-radius: 8px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.stat-item {
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 1.3rem;
|
||
font-weight: 700;
|
||
color: #3498db;
|
||
}
|
||
|
||
.stat-label {
|
||
color: #7f8c8d;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
footer {
|
||
margin-top: 40px;
|
||
text-align: center;
|
||
color: #7f8c8d;
|
||
font-size: 0.9rem;
|
||
padding: 20px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.container {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.controls {
|
||
width: 100%;
|
||
max-width: 700px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<h1>Canvas图片拖放功能</h1>
|
||
<p class="subtitle">将橙色角色拖动到目标区域,体验HTML5 Canvas的交互功能</p>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<div class="canvas-container">
|
||
<h2>拖放画布</h2>
|
||
<canvas id="myCanvas" width="650" height="500"></canvas>
|
||
<div class="character-info">
|
||
<p><strong>角色说明:</strong>这是一个橙色卡通角色,正在向后转身。角色背部有白色椭圆形特征,右臂向后摆动,左臂略微向前,呈现动态效果。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<div class="control-group">
|
||
<h3>操作说明</h3>
|
||
<ul class="instructions">
|
||
<li>1. 在左侧画布上点击并按住橙色角色</li>
|
||
<li>2. 拖动角色到任意位置</li>
|
||
<li>3. 将角色移动到下方目标区域</li>
|
||
<li>4. 释放鼠标放下角色</li>
|
||
</ul>
|
||
|
||
<div class="target-area" id="targetArea">
|
||
<strong>目标区域</strong><br>
|
||
将角色拖放到这里
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stats">
|
||
<div class="stat-item">
|
||
<div class="stat-value" id="dragCount">0</div>
|
||
<div class="stat-label">拖放次数</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-value" id="targetHits">0</div>
|
||
<div class="stat-label">命中目标</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-value" id="distance">0</div>
|
||
<div class="stat-label">移动距离(px)</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status" id="status">
|
||
点击并拖动角色开始体验
|
||
</div>
|
||
|
||
<button class="reset-btn" id="resetBtn">重置角色位置</button>
|
||
</div>
|
||
</div>
|
||
|
||
<footer>
|
||
<p>HTML5 Canvas 拖放功能示例 | 基于用户提供的图片描述实现</p>
|
||
</footer>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 获取Canvas和上下文
|
||
const canvas = document.getElementById('myCanvas');
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
// 获取DOM元素
|
||
const status = document.getElementById('status');
|
||
const resetBtn = document.getElementById('resetBtn');
|
||
const targetArea = document.getElementById('targetArea');
|
||
const dragCountElement = document.getElementById('dragCount');
|
||
const targetHitsElement = document.getElementById('targetHits');
|
||
const distanceElement = document.getElementById('distance');
|
||
|
||
// 图片对象
|
||
const character = {
|
||
x: 100,
|
||
y: 100,
|
||
width: 150,
|
||
height: 200,
|
||
isDragging: false,
|
||
offsetX: 0,
|
||
offsetY: 0,
|
||
color: '#FF8C00', // 橙色
|
||
text: 'A'
|
||
};
|
||
|
||
// 目标区域
|
||
const target = {
|
||
x: 400,
|
||
y: 300,
|
||
width: 200,
|
||
height: 150
|
||
};
|
||
|
||
// 统计数据
|
||
let stats = {
|
||
dragCount: 0,
|
||
targetHits: 0,
|
||
totalDistance: 0
|
||
};
|
||
|
||
// 更新统计显示
|
||
function updateStats() {
|
||
dragCountElement.textContent = stats.dragCount;
|
||
targetHitsElement.textContent = stats.targetHits;
|
||
distanceElement.textContent = Math.round(stats.totalDistance);
|
||
}
|
||
|
||
// 绘制角色
|
||
function drawCharacter() {
|
||
// 绘制角色主体
|
||
ctx.fillStyle = character.color;
|
||
|
||
// 绘制身体
|
||
ctx.beginPath();
|
||
ctx.ellipse(
|
||
character.x + character.width/2,
|
||
character.y + character.height/2,
|
||
character.width/3,
|
||
character.height/2.5,
|
||
0, 0, Math.PI * 2
|
||
);
|
||
ctx.fill();
|
||
|
||
// 绘制头部
|
||
ctx.beginPath();
|
||
ctx.arc(character.x + character.width/2, character.y + 40, 30, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// 绘制背部白色标记
|
||
ctx.fillStyle = 'white';
|
||
ctx.beginPath();
|
||
ctx.ellipse(
|
||
character.x + character.width/2,
|
||
character.y + character.height/2,
|
||
15,
|
||
25,
|
||
0, 0, Math.PI * 2
|
||
);
|
||
ctx.fill();
|
||
|
||
// 绘制四肢
|
||
ctx.fillStyle = character.color;
|
||
|
||
// 左臂
|
||
ctx.fillRect(character.x + 10, character.y + 80, 20, 60);
|
||
|
||
// 右臂
|
||
ctx.fillRect(character.x + character.width - 30, character.y + 80, 20, 60);
|
||
|
||
// 左腿
|
||
ctx.fillRect(character.x + 40, character.y + 150, 20, 50);
|
||
|
||
// 右腿
|
||
ctx.fillRect(character.x + 90, character.y + 150, 20, 50);
|
||
|
||
// 绘制鞋子
|
||
ctx.fillStyle = '#333';
|
||
ctx.fillRect(character.x + 35, character.y + 195, 30, 10);
|
||
ctx.fillRect(character.x + 85, character.y + 195, 30, 10);
|
||
|
||
// 绘制面部特征
|
||
ctx.fillStyle = 'white';
|
||
ctx.beginPath();
|
||
ctx.arc(character.x + character.width/2 - 10, character.y + 35, 5, 0, Math.PI * 2);
|
||
ctx.arc(character.x + character.width/2 + 10, character.y + 35, 5, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// 绘制嘴部
|
||
ctx.beginPath();
|
||
ctx.arc(character.x + character.width/2, character.y + 50, 8, 0, Math.PI);
|
||
ctx.stroke();
|
||
|
||
// 绘制角色上的文字
|
||
ctx.fillStyle = 'white';
|
||
ctx.font = 'bold 24px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(character.text, character.x + character.width/2, character.y + 100);
|
||
|
||
// 如果正在拖拽,绘制高亮边框
|
||
if (character.isDragging) {
|
||
ctx.strokeStyle = '#3498db';
|
||
ctx.lineWidth = 3;
|
||
ctx.setLineDash([5, 5]);
|
||
ctx.strokeRect(character.x - 5, character.y - 5, character.width + 10, character.height + 10);
|
||
ctx.setLineDash([]);
|
||
}
|
||
}
|
||
|
||
// 绘制目标区域
|
||
function drawTarget() {
|
||
ctx.fillStyle = 'rgba(52, 152, 219, 0.1)';
|
||
ctx.strokeStyle = '#3498db';
|
||
ctx.lineWidth = 2;
|
||
ctx.setLineDash([5, 5]);
|
||
ctx.fillRect(target.x, target.y, target.width, target.height);
|
||
ctx.strokeRect(target.x, target.y, target.width, target.height);
|
||
ctx.setLineDash([]);
|
||
|
||
// 绘制目标区域内的文字
|
||
ctx.fillStyle = '#3498db';
|
||
ctx.font = 'bold 20px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('放置区域', target.x + target.width/2, target.y + target.height/2);
|
||
}
|
||
|
||
// 绘制Canvas
|
||
function drawCanvas() {
|
||
// 清除Canvas
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
|
||
// 绘制目标区域
|
||
drawTarget();
|
||
|
||
// 绘制角色
|
||
drawCharacter();
|
||
|
||
// 检查角色是否在目标区域内
|
||
checkTargetHit();
|
||
}
|
||
|
||
// 检查角色是否在目标区域内
|
||
function checkTargetHit() {
|
||
const charCenterX = character.x + character.width/2;
|
||
const charCenterY = character.y + character.height/2;
|
||
|
||
const isInTarget =
|
||
charCenterX > target.x &&
|
||
charCenterX < target.x + target.width &&
|
||
charCenterY > target.y &&
|
||
charCenterY < target.y + target.height;
|
||
|
||
if (isInTarget) {
|
||
targetArea.classList.add('active');
|
||
status.textContent = "成功!角色已在目标区域内";
|
||
status.className = "status success";
|
||
} else {
|
||
targetArea.classList.remove('active');
|
||
|
||
if (character.isDragging) {
|
||
status.textContent = "拖动角色到目标区域";
|
||
status.className = "status dragging";
|
||
} else {
|
||
status.textContent = "点击并拖动角色开始体验";
|
||
status.className = "status";
|
||
}
|
||
}
|
||
|
||
return isInTarget;
|
||
}
|
||
|
||
// 检查点击是否在角色上
|
||
function isPointInCharacter(x, y) {
|
||
return x > character.x &&
|
||
x < character.x + character.width &&
|
||
y > character.y &&
|
||
y < character.y + character.height;
|
||
}
|
||
|
||
// 计算两点之间的距离
|
||
function calculateDistance(x1, y1, x2, y2) {
|
||
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
|
||
}
|
||
|
||
// 鼠标按下事件
|
||
function handleMouseDown(e) {
|
||
const rect = canvas.getBoundingClientRect();
|
||
const mouseX = e.clientX - rect.left;
|
||
const mouseY = e.clientY - rect.top;
|
||
|
||
if (isPointInCharacter(mouseX, mouseY)) {
|
||
character.isDragging = true;
|
||
character.offsetX = mouseX - character.x;
|
||
character.offsetY = mouseY - character.y;
|
||
|
||
// 记录开始拖拽的位置
|
||
character.startDragX = character.x;
|
||
character.startDragY = character.y;
|
||
|
||
drawCanvas();
|
||
}
|
||
}
|
||
|
||
// 鼠标移动事件
|
||
function handleMouseMove(e) {
|
||
if (!character.isDragging) return;
|
||
|
||
const rect = canvas.getBoundingClientRect();
|
||
const mouseX = e.clientX - rect.left;
|
||
const mouseY = e.clientY - rect.top;
|
||
|
||
// 计算移动距离
|
||
const distanceMoved = calculateDistance(
|
||
character.x + character.width/2,
|
||
character.y + character.height/2,
|
||
mouseX - character.offsetX + character.width/2,
|
||
mouseY - character.offsetY + character.height/2
|
||
);
|
||
|
||
stats.totalDistance += distanceMoved;
|
||
|
||
// 更新角色位置
|
||
character.x = mouseX - character.offsetX;
|
||
character.y = mouseY - character.offsetY;
|
||
|
||
// 限制角色不超出Canvas边界
|
||
character.x = Math.max(0, Math.min(canvas.width - character.width, character.x));
|
||
character.y = Math.max(0, Math.min(canvas.height - character.height, character.y));
|
||
|
||
drawCanvas();
|
||
updateStats();
|
||
}
|
||
|
||
// 鼠标释放事件
|
||
function handleMouseUp() {
|
||
if (character.isDragging) {
|
||
character.isDragging = false;
|
||
stats.dragCount++;
|
||
|
||
// 检查是否在目标区域内
|
||
if (checkTargetHit()) {
|
||
stats.targetHits++;
|
||
}
|
||
|
||
drawCanvas();
|
||
updateStats();
|
||
}
|
||
}
|
||
|
||
// 重置角色位置
|
||
function resetCharacter() {
|
||
character.x = 100;
|
||
character.y = 100;
|
||
character.isDragging = false;
|
||
|
||
// 重置目标区域状态
|
||
targetArea.classList.remove('active');
|
||
status.textContent = "点击并拖动角色开始体验";
|
||
status.className = "status";
|
||
|
||
drawCanvas();
|
||
}
|
||
|
||
// 初始化
|
||
function init() {
|
||
drawCanvas();
|
||
|
||
// 添加事件监听器
|
||
canvas.addEventListener('mousedown', handleMouseDown);
|
||
canvas.addEventListener('mousemove', handleMouseMove);
|
||
canvas.addEventListener('mouseup', handleMouseUp);
|
||
|
||
// 添加重置按钮事件监听器
|
||
resetBtn.addEventListener('click', resetCharacter);
|
||
|
||
// 更新统计
|
||
updateStats();
|
||
}
|
||
|
||
// 启动应用
|
||
init();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |