目录
橡皮筋的特点是可以拉长与缩短,那么如何在canvas的绘图当中模仿这一特点呢?就是如何产生以下的这种类似橡皮筋的效果呢?
在canvas绘图中,这种效果的原理是不断的清空画板且不断的绘制图形,而清空画板利用了canvas绘图表面的保存与恢复的机制。
一、使用橡皮筋技术绘制线段
首先来看一个使用橡皮筋技术绘制线段的示例:
源代码:
drawRubberbandLine.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>绘制橡皮筋式线条</title>
<style>
body {
background: #eee;
}
#canvas {
background: #fff;
cursor: pointer;
margin-left: 10px;
margin-top: 10px;
-webkit-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
-moz-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById('canvas');
let context = canvas.getContext("2d");
let drawingSurface = null; // 用于保存绘图表面
let mouseDown = null; // 用于保存鼠标按下时的canvas坐标
let dragging = false; // 用于标识鼠标是否处于拖拽状态,只有拖拽状态才可以进行绘制
// 将浏览器客户区坐标转换为canvas坐标
function windowToCanvas(clientX, clientY){
let bbox = canvas.getBoundingClientRect();
return {
x: (clientX - bbox.left) / (canvas.width / bbox.width),
y: (clientY - bbox.top) / (canvas.height / bbox.height)
};
}
// 保存canvas绘图表面
function saveDrawingSurface(){
drawingSurface = context.getImageData(0, 0, canvas.width, canvas.height);
}
// 恢复canvas绘图表面
function restoreDrawingSurface(){
context.putImageData(drawingSurface, 0, 0);
}
// 绘制线段
function drawLine(mouseMove){
context.beginPath(); // 清除当前路径
context.moveTo(mouseDown.x, mouseDown.y);
context.lineTo(mouseMove.x, mouseMove.y);
context.strokeStyle = "green";
context.stroke();
}
canvas.addEventListener("mousedown", (event) => {
// 保存鼠标按下时的canvas坐标
mouseDown = windowToCanvas(event.clientX, event.clientY);
// 标识鼠标处于拖拽状态
dragging = true;
// 保存canvas绘图表面
saveDrawingSurface();
});
canvas.addEventListener("mousemove", (event) => {
if(dragging){
// 恢复绘图表面
restoreDrawingSurface();
let mouseMove = windowToCanvas(event.clientX, event.clientY);
// 绘制线段
drawLine(mouseMove);
}
});
canvas.addEventListener("mouseup", (event) => {
// 标识鼠标不在拖拽状态
dragging = false;
});
</script>
</body>
</html>
效果:
代码并不复杂,关键是使用了getImageData()方法和putImageData()方法来保存和恢复绘图表面,以使绘图表面不断清空。
二、使用橡皮筋技术绘制圆
可以使用橡皮筋技术让线段产生橡皮筋的效果,同时也可以使用橡皮筋技术让圆的半径随意放大与缩小。
来看使用橡皮筋技术绘制圆的示例:
源代码:
drawRubberbandCircle.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用橡皮筋技术绘制圆</title>
<style>
body {
background: #eee;
}
#canvas {
background: #fff;
cursor: pointer;
margin-left: 10px;
margin-top: 10px;
-webkit-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
-moz-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById('canvas');
let context = canvas.getContext("2d");
let drawingSurface = null; // 用于保存绘图表面
let mouseDown = null; // 用于保存鼠标按下时的canvas坐标
let dragging = false; // 用于标识鼠标是否处于拖拽状态,只有拖拽状态才可以进行绘制
// 将浏览器客户区坐标转换为canvas坐标
function windowToCanvas(clientX, clientY){
let bbox = canvas.getBoundingClientRect();
return {
x: (clientX - bbox.left) / (canvas.width / bbox.width),
y: (clientY - bbox.top) / (canvas.height / bbox.height)
};
}
// 保存canvas绘图表面
function saveDrawingSurface(){
drawingSurface = context.getImageData(0, 0, canvas.width, canvas.height);
}
// 恢复canvas绘图表面
function restoreDrawingSurface(){
context.putImageData(drawingSurface, 0, 0);
}
// 绘制圆
function drawCircle(mouseMove){
context.beginPath(); // 清除当前路径
// 使用勾股定理圆的计算半径
let r = Math.sqrt(Math.pow((mouseMove.x-mouseDown.x), 2) + Math.pow((mouseMove.y-mouseDown.y), 2));
// 绘制圆
context.arc(mouseDown.x, mouseDown.y, r, 0, 2*Math.PI, true);
context.strokeStyle = "blue";
context.stroke();
}
canvas.addEventListener("mousedown", (event) => {
// 保存鼠标按下时的canvas坐标
mouseDown = windowToCanvas(event.clientX, event.clientY);
// 标识鼠标处于拖拽状态
dragging = true;
// 保存canvas绘图表面
saveDrawingSurface();
});
canvas.addEventListener("mousemove", (event) => {
if(dragging){
// 恢复绘图表面
restoreDrawingSurface();
let mouseMove = windowToCanvas(event.clientX, event.clientY);
// 绘制圆
drawCircle(mouseMove);
}
});
canvas.addEventListener("mouseup", (event) => {
// 标识鼠标不在拖拽状态
dragging = false;
});
</script>
</body>
</html>
效果:
怎么样,圆的半径是不是可以伴随着鼠标的拖拽随意放大缩小呢?这样绘制圆就容易多了,半径想多大就多大!
这里绘制圆使用了勾股定理求半径,当然用三角函数也是可以轻松求得的,但是个人感觉还是勾股定理比较简单方便。
三、使用橡皮筋技术绘制多边形
由于绘制多边形的原理就是使用多边形的外接圆半径求多边形的各个顶点,所以也可以将橡皮筋技术运用在绘制多边形中。
所以外接圆半径一改变,多边形大小就跟着改变。
使用橡皮筋技术绘制多边形的示例:
源代码drawRubberbandPolygon.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用橡皮筋技术绘制多边形</title>
<style>
body {
background: #eee;
}
#canvas {
background: #fff;
cursor: pointer;
margin-left: 10px;
margin-top: 10px;
-webkit-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
-moz-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
}
</style>
</head>
<body>
<input type="number" id="sides" min="3", max="1000", value="3", step="1" />多边形边数</br>
<canvas id="canvas" width="600" height="400"></canvas>
<script>
let canvas = document.getElementById('canvas');
let context = canvas.getContext("2d");
let sides = document.getElementById("sides");
let drawingSurface = null; // 用于保存绘图表面
let mouseDown = null; // 用于保存鼠标按下时的canvas坐标
let dragging = false; // 用于标识鼠标是否处于拖拽状态,只有拖拽状态才可以进行绘制
// 将浏览器客户区坐标转换为canvas坐标
function windowToCanvas(clientX, clientY){
let bbox = canvas.getBoundingClientRect();
return {
x: (clientX - bbox.left) / (canvas.width / bbox.width),
y: (clientY - bbox.top) / (canvas.height / bbox.height)
};
}
// 保存canvas绘图表面
function saveDrawingSurface(){
drawingSurface = context.getImageData(0, 0, canvas.width, canvas.height);
}
// 恢复canvas绘图表面
function restoreDrawingSurface(){
context.putImageData(drawingSurface, 0, 0);
}
// 绘制多边形
function drawPolygon(mouseMove){
context.beginPath(); // 清除当前路径
// 多边形外接圆半径
let r = Math.sqrt(Math.pow((mouseDown.x - mouseMove.x), 2) + Math.pow((mouseDown.y - mouseMove.y), 2));
let angle = 0;
for(let i = 0; i < sides.value; i++){
let vertex_x = mouseDown.x + r*Math.sin(angle);
let vertex_y = mouseDown.y - r*Math.cos(angle);
if(i == 0){
// 多边形起始顶点
context.moveTo(vertex_x, vertex_y);
}else{
// 多边形其余顶点
context.lineTo(vertex_x, vertex_y);
}
angle += 2 * Math.PI / sides.value;
}
context.closePath();
context.stroke();
}
canvas.addEventListener("mousedown", (event) => {
// 保存鼠标按下时的canvas坐标
mouseDown = windowToCanvas(event.clientX, event.clientY);
// 标识鼠标处于拖拽状态
dragging = true;
// 保存canvas绘图表面
saveDrawingSurface();
});
canvas.addEventListener("mousemove", (event) => {
if(dragging){
// 恢复绘图表面
restoreDrawingSurface();
let mouseMove = windowToCanvas(event.clientX, event.clientY);
// 绘制线段
drawPolygon(mouseMove);
}
});
canvas.addEventListener("mouseup", (event) => {
// 标识鼠标不在拖拽状态
dragging = false;
});
</script>
</body>
</html>
效果:
当把多边形的边调大到比较大的值时(比如999),就验证了微积分的思想,因为此时多边形"变成"了圆:
源代码使用了简单的三角函数来求多边形的顶点。