《canvas》之第17章 用户交互
第17章 用户交互
17.1 用户交互简介
用户借助鼠标或键盘参与canvas动画,实现互动效果。
17.2 捕获物体
17.2.1 捕获物体简介
判断鼠标点击时是否落在物体边界范围内。
多边形及不规则图形的捕获,采用分离轴定理(SAT,Separating Axis Theorem)和最小平移向量(MTV)。
-
分离轴定理(只适合凸多边形,所以如果是凹多边形的话需要转换成多个凸多边形)
通过判断任意两个凸多边形在任意角度(每个边的法向量)下的投影是否均存在重叠,来判断是否发生碰撞。即两个不相交的多边形一定能找到一条轴,它们在这条轴上的投影不相交,可以理解为存在一个角度用电筒照这两个不相交多边形得到不相交的投影。 -
最小平移向量(Minimum Translation Vector, MTV)
两个物体重叠或分离的最小距离,可以基于分离轴定理计算。
- 矩形的捕获
if (mouse.x > rect.x &&
mouse.x < rect.x + rect.width &&
mouse.y > rect.y &&
mouse.y < rec.y + rect.height) {
//...
}
- 圆的捕获
dx = mouse.x - ball.x;
dy = mouse.y - ball.y;
distance = Math.sqrt(dx*dx+dy*dy);
if(distance < ball.radius) {
//...
}
17.2.2 捕获静止物体
//检测是否捕获了小球
checkMouse:function(mouse) {
var dx = mouse.x - this.x;
var dy = mouse.y - this.y;
var distance = Math.sqrt(dx*dx+dy*dy);
if(distance < this.radius) {
return true;
} else {
return false;
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var txt = document.getElementById("txt");
var ball = new Ball(cnv.width / 2, cnv.height / 2, 30);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
//添加鼠标移动事件
cnv.addEventListener("mousemove", function () {
//判断鼠标当前坐标是否处于小球内
if (ball.checkMouse(mouse)) {
txt.innerHTML = "鼠标移入小球";
} else {
txt.innerHTML = "鼠标移出小球";
}
}, false);
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
<p id="txt"></p>
</body>
</html>
17.2.3 捕获运动物体
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(0, cnv.height / 2, 20);
var mouse = tools.getMouse(cnv);
//isMouseDown用于标识鼠标是否按下的状态
var isMouseDown = false;
var vx = 3;
cnv.addEventListener("mousedown", function () {
//判断鼠标点击坐标是否位于小球上,如果是,则isMouseDown为true
if (ball.checkMouse(mouse)) {
isMouseDown = true;
alert("捕获成功");
}
}, false);
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//如果鼠标不是按下状态,则小球继续运动,否则就会停止
if (!isMouseDown) {
ball.x += vx;
}
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 添加边界反弹效果
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(cnv.width / 2, cnv.height / 2, 20);
var mouse = tools.getMouse(cnv);
var isMouseDown = false;
//随机产生-3~3之间的任意数,作为vx、vy的值
var vx = (Math.random() * 2 - 1) * 3;
var vy = (Math.random() * 2 - 1) * 3;
//为画布添加mousedown事件
cnv.addEventListener("mousedown", function () {
var rect = ball.getRect();
if (ball.checkMouse(mouse)) {
isMouseDown = true;
alert("捕获成功");
}
}, false);
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//如果鼠标不是按下状态,则小球继续运动,否则就会停止
if (!isMouseDown) {
ball.x += vx;
ball.y += vy;
//边界检测
//碰到左边界
if (ball.x < ball.radius) {
ball.x = ball.radius;
vx = -vx;
}
//碰到右边界
else if (ball.x > canvas.width - ball.radius) {
ball.x = canvas.width - ball.radius;
vx = -vx;
}
//碰到上边界
if (ball.y < ball.radius) {
ball.y = ball.radius;
vy = -vy;
}
//碰到下边界
else if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.height - ball.radius;
vy = -vy;
}
}
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
17.3 拖曳物体
- 捕获物体:mousedown,判断鼠标指针是否落在物体上,是则添加mousemove和mouseup。
- 移动物体:mousemove时,更新物体坐标为鼠标指针的坐标。
- 松开物体:mouseup时,移除mouseup和mousemove事件。
cnv.addEventListener("mousedown", function() {
cnv.addEventListener("mousedown", onMouseMove, false);
cnv.addEventListener("mouseup", onMouseUp, false);
}, false);
- 拖曳物体
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(cnv.width / 2, cnv.height / 2, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
//为Canvas添加鼠标按下事件(mousedown)
cnv.addEventListener("mousedown", function () {
//判断鼠标点击是否落在小球上,如果落在,就添加两个事件:mousemove、mouseup
if (ball.checkMouse(mouse)) {
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//鼠标移动时,更新小球坐标
ball.x = mouse.x;
ball.y = mouse.y;
}
function onMouseUp() {
//鼠标松开时,移除鼠标松开事件:mouseup(自身事件)
document.removeEventListener("mouseup", onMouseUp, false);
//鼠标松开时,移除鼠标移动事件:mousemove
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 修复bug
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(cnv.width / 2, cnv.height / 2, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
//初始化2个变量:dx和dy
var dx = 0, dy = 0;
cnv.addEventListener("mousedown", function () {
if (ball.checkMouse(mouse)) {
//dx为鼠标与球心的水平偏移量
dx = mouse.x - ball.x;
//dy为鼠标与球心的垂直偏移量
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//更新小球坐标
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
}
function onMouseUp() {
//鼠标松开时,移除鼠标松开事件:mouseup(自身事件)
document.removeEventListener("mouseup", onMouseUp, false);
//鼠标松开时,移除鼠标移动事件:mousemove
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 加入边界限制
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(cnv.width / 2, cnv.height / 2, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
var dx = 0, dy = 0;
cnv.addEventListener("mousedown", function () {
if (ball.checkMouse(mouse)) {
//dx为鼠标与球心的水平偏移量
dx = mouse.x - ball.x;
//dy为鼠标与球心的垂直偏移量
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//更新小球坐标
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
//加入边界限制
//当小球碰到左边界时
if (ball.x < ball.radius) {
ball.x = ball.radius;
//当小球碰到右边界时
} else if (ball.x > cnv.width - ball.radius) {
ball.x = cnv.width - ball.radius;
}
//当小球碰到上边界时
if (ball.y < ball.radius) {
ball.y = ball.radius;
//当小球碰到下边界时
} else if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
}
}
function onMouseUp() {
document.removeEventListener("mouseup", onMouseUp, false);
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
17.4 抛掷物体
选中物体,拖曳着向某个方向移动,松开鼠标后物体沿拖曳方向继续前进。
拖曳过程中计算物体的速度向量,释放物体时将这个速度向量赋给物体。
- 抛掷物体
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(cnv.width / 2, cnv.height / 2, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
var isMouseDown = false;
var dx = 0, dy = 0;
//oldX和oldY用于存储小球旧的坐标
var oldX, oldY;
//初始速度vx和vy都为0
var vx = 0, vy = 0;
//添加mousedown事件
cnv.addEventListener("mousedown", function () {
//判断鼠标点击是否落在小球上
if (ball.checkMouse(mouse)) {
//鼠标按下小球时,isMouseDown设置为true
isMouseDown = true;
//鼠标按下小球时,将当前鼠标位置赋值给oldX和oldY
oldX = ball.x;
oldY = ball.y;
dx = mouse.x - ball.x;
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//鼠标移动时,更新小球坐标
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
}
function onMouseUp() {
//鼠标松开时,isMouseDown设置为false
isMouseDown = false;
document.removeEventListener("mouseup", onMouseUp, false);
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
if(isMouseDown) {
//如果isMouseDown为true,用当前小球的位置减去上一帧的坐标
vx = ball.x - oldX;
vy = ball.y - oldY;
//如果isMouseDown为true,更新oldX和oldY为当前小球中心坐标
oldX = ball.x;
oldY = ball.y;
} else {
//如果isMouseDown为false,小球沿着抛掷方向运动
ball.x += vx;
ball.y += vy;
}
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="300" height="200" style="border:1px solid silver;"></canvas>
</body>
</html>
- 加入边界限制
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(cnv.width / 2, cnv.height / 2, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
var isMouseDown = false;
var dx = 0, dy = 0;
//oldX和oldY用于存储小球旧的坐标
var oldX, oldY;
//初始速度vx和vy都为0
var vx = 0, vy = 0;
//添加mousedown事件
cnv.addEventListener("mousedown", function () {
//判断鼠标点击是否落在小球上
if (ball.checkMouse(mouse)) {
//鼠标按下小球时,isMouseDown设置为true
isMouseDown = true;
//鼠标按下小球时,将当前鼠标位置赋值给oldX和oldY
oldX = ball.x;
oldY = ball.y;
dx = mouse.x - ball.x;
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//鼠标移动时,更新小球坐标
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
//加入边界限制
//当小球碰到左边界时
if (ball.x < ball.radius) {
ball.x = ball.radius;
//当小球碰到右边界时
} else if (ball.x > cnv.width - ball.radius) {
ball.x = cnv.width - ball.radius;
}
//当小球碰到上边界时
if (ball.y < ball.radius) {
ball.y = ball.radius;
//当小球碰到下边界时
} else if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
}
}
function onMouseUp() {
//鼠标松开时,isMouseDown设置为false
isMouseDown = false;
document.removeEventListener("mouseup", onMouseUp, false);
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
if(isMouseDown) {
//如果isMouseDown为true,用当前小球的位置减去上一帧的坐标
vx = ball.x - oldX;
vy = ball.y - oldY;
//如果isMouseDown为true,更新oldX和oldY为当前小球中心坐标
oldX = ball.x;
oldY = ball.y;
} else {
//如果isMouseDown为false,小球沿着抛掷方向运动
ball.x += vx;
ball.y += vy;
//边界反弹
//碰到右边界
if(ball.x > cnv.width - ball.radius) {
ball.x = cnv.width - ball.radius;
vx = -vx;
//碰到左边界
} else if (ball.x < ball.radius) {
ball.x = ball.radius;
vx = -vx;
}
//碰到下边界
if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
vy = -vy;
//碰到下边界
} else if (ball.y < ball.radius) {
ball.y = ball.radius;
vy = -vy;
}
}
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="300" height="200" style="border:1px solid silver;"></canvas>
</body>
</html>
- 加入重力和反弹消耗
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(cnv.width / 2, cnv.height, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
var isMouseDown = false;
var dx = 0, dy = 0;
//oldX和oldY用于存储小球旧的坐标
var oldX, oldY;
//初始速度vx和vy都为0
var vx = 0, vy = 0;
//加入重力和反弹消耗
var gravity = 1.5;
var bounce = -0.8;
cnv.addEventListener("mousedown", function () {
//判断鼠标点击是否落在小球上
if (ball.checkMouse(mouse)) {
//鼠标按下小球时,isMouseDown设置为true
isMouseDown = true;
//鼠标按下小球时,将当前鼠标位置赋值给oldX和oldY
oldX = ball.x;
oldY = ball.y;
dx = mouse.x - ball.x;
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//鼠标移动时,更新小球坐标
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
//加入边界限制
//当小球碰到左边界时
if (ball.x < ball.radius) {
ball.x = ball.radius;
//当小球碰到右边界时
} else if (ball.x > cnv.width - ball.radius) {
ball.x = cnv.width - ball.radius;
}
//当小球碰到上边界时
if (ball.y < ball.radius) {
ball.y = ball.radius;
//当小球碰到下边界时
} else if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
}
}
function onMouseUp() {
//鼠标松开时,isMouseDown设置为false
isMouseDown = false;
document.removeEventListener("mouseup", onMouseUp, false);
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
if (isMouseDown) {
//如果isMouseDown为true,用当前小球的位置减去上一帧的坐标
vx = ball.x - oldX;
vy = ball.y - oldY;
//如果isMouseDown为true,更新oldX和oldY为当前小球中心坐标
oldX = ball.x;
oldY = ball.y;
} else {
//如果isMouseDown为false,小球沿着抛掷方向运动
vy += gravity;
ball.x += vx;
ball.y += vy;
//边界检测
//碰到右边界
if (ball.x > canvas.width - ball.radius) {
ball.x = canvas.width - ball.radius;
vx = vx * bounce;
//碰到左边界
} else if (ball.x < ball.radius) {
ball.x = ball.radius;
vx = vx * bounce;
}
//碰到下边界
if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.height - ball.radius;
vy = vy * bounce;
//碰到下边界
} else if (ball.y < ball.radius) {
ball.y = ball.radius;
vy = vy * bounce;
}
}
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="300" height="200" style="border:1px solid silver;"></canvas>
</body>
</html>