这两天有人问了上面这个标题的问题,老实说,我一个后端程序员,感觉还是有一点难度的,主要是好多东西都忘记了。正好星期天了,抽时间弄一个简单的实现出来,正好也能写一篇博客了。
效果演示
分析
创建一个缓存的canvas对象,保存当前canvas中的内容,在屏幕canvas上绘制图像时,同时也在缓存canvas中进行等比例的绘制。然后,在缩放操作时,首先要清空当前 canvas 的内容,然后把缓存 canvas 的内容,绘制到当前canvas中,即可实现该效果了。这里的难点在于等比例绘制时的对应关系。我在当前canvas上设置的是红色画笔,但是缓存 canvas 中,没有设置(默认是黑色),所以你可以看到我缩放的时候,所显示的画笔颜色里面变黑了。实际上不是变成黑色了,而是当前的 canvas 被缓存的 canvas 内容覆盖了,因为是等比例绘制,所以看起来的感觉是画笔变黑了(哈哈,已绘制的线条是无法变黑的,这是canvas的机制决定的,立即模式)。
代码
通过 http 协议在浏览器访问这个页面即可(同目录下放一张图片,注意名字和代码里面的一致)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
#cas {
clear: left;
width: 800px;
border: #e90d0d 1px solid;
}
</style>
</head>
<body>
<div id="cas">
<canvas id="cs" width="800" height="800"></canvas>
</div>
<script type="text/javascript">
var canvas = document.getElementById("cs"); //获取画布
var ctx = canvas.getContext("2d");
var cacheCanvas = document.createElement("canvas"); // 缓存画布
var cacheCtx = cacheCanvas.getContext("2d");
cacheCanvas.width = canvas.width;
cacheCanvas.height = canvas.height;
ctx.lineWidth = 10;
ctx.strokeStyle = "#e90d0d"
var img = new Image();
img.src = "./husky.png";
img.onload = () => {
ctx.drawImage(img, 0, 0);
cacheCtx.drawImage(canvas, 0, 0); // 加载完即将当前画布内容写入缓存画布
}
/* 用户绘制的动作,可以分解为如下操作:
1.按下鼠标
2.移动鼠标
3.松开鼠标
它们分别对应于鼠标的onmousedown、onmousemove和onmouseup事件。
并且上述操作必然是有想后顺序的,因为人的操作必然是几个操作
集合中的一种。所以我们需要来限定以下,过滤用户的无效操作,
只对按照上诉顺序的操作进行响应。
*/
let isDowned = false; // 是否按下鼠标,默认是false,如果为false,则不响应任何事件。
// 开始添加鼠标事件
canvas.onmousedown = (e) => {
let x = e.clientX - canvas.offsetLeft;
let y = e.clientY - canvas.offsetTop;
isDowned = true; // 设置isDowned为true,可以响应鼠标移动事件
console.log("当前鼠标点击的坐标为:(", x + ", " + y + ")");
ctx.beginPath(); // 开始一个新的路径
ctx.moveTo(x, y); // 移动画笔到鼠标的点击位置
// 在缓存canvas中按照比例进行等比例缩小绘制
cacheCtx.lineWidth = ctx.lineWidth/ZOOM[zoom_pos];
cacheCtx.beginPath();
let pos = getScalePos(ZOOM[zoom_pos], x, y);
cacheCtx.moveTo(pos.x, pos.y);
}
canvas.onmousemove = (e) => {
if (!isDowned) {
return ;
}
let x = e.clientX - canvas.offsetLeft;
let y = e.clientY - canvas.offsetTop;
ctx.lineTo(x, y); // 移动画笔绘制线条
ctx.stroke();
// 在缓存canvas中按照比例进行等比例缩小绘制
let pos = getScalePos(ZOOM[zoom_pos], x, y);
cacheCtx.lineTo(pos.x, pos.y);
cacheCtx.stroke();
}
function getScalePos(size, x , y) {
// 计算放大后,等比例对应的位置,这里需要多理解。
return {
x: (canvas.width*(size-1)/2+x)/size,
y: (canvas.height*(size-1)/2+y)/size
}
}
canvas.onmouseup = (e) => {
isDowned = false;
}
/*
在按下鼠标移动的过程中,如果移出了画布,则无法触发鼠标松开事件,即onmouseup。
所以需要在鼠标移出画布时,设置isDowned为false。
*/
canvas.onmouseout = (e) => {
isDowned = false;
}
// 定义一个缩放数组,初始在 1.0 位置
const ZOOM = [1.0, 1.2, 1.4, 1.6, 1.8, 2.0];
let zoom_pos = 0;
canvas.onmousewheel = (e) => {
let x = e.clientX - canvas.offsetLeft;
let y = e.clientY - canvas.offsetTop;
delta = e.wheelDelta;
if (delta > 0) {
if (zoom_pos + 1 < ZOOM.length) {
zoom_pos += 1;
} else {
zoom_pos = ZOOM.length - 1;
}
} else {
if (zoom_pos - 1 >= 0) {
zoom_pos -= 1;
} else {
zoom_pos = 0;
}
}
let xx = 0, yy = 0, dd = 0, hh = 0;
let size = ZOOM[zoom_pos];
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
// 简单的等比例扩大,以canvas为中心。
ctx.drawImage(cacheCanvas, (1-size)*canvas.width/2, (1-size)*canvas.height/2, canvas.width*size, canvas.height*size);
ctx.restore();
console.log(size);
}
</script>
</body>
</html>
不足
我自己都有大半年没有接触 Canvas 了,里面的 api 我都快忘记了,不过这个问题蛮有趣的,我想了好久,也看了一些资料。不过,基本上只有缩放,或者只有绘制的,两种结合的没有。可能也是搜索的方式不太对吧。这个博客写得很急,代码也很乱,不过看思路就好了。代码,说不定还有 bug 没有发现呢,哈哈。