马黑黑 发表于 2022-3-9 09:53

HTML5画布:圆的精准删除及运动演示

<div style="text-align:center">
        <canvas id="canvas" width="400" height="300"></canvas><br>
        <input id="del" type="button" value="删除圆形" />
        <input id="marq" type="button" value="停止运动" />
</div>

<script>

var flag = 0; //删除操作标识
var cvs = document.getElementById('canvas'); //画布句柄
var cvt = cvs.getContext('2d'); //画布句柄
var rr = 10; // 小球半径
var dx = cvs.clientWidth/2; //小球水平位置
var dy = 20; //小球垂直起始位置
var speed = 1; //小球行进速度

draw(); //绘制左上角图案
var start = setInterval(marq,10); //小球运动

//按钮1单击事件
document.getElementById('del').onclick = function(){
        if(flag == 0) {
                cvt.clearRect(10,10,100,100); //擦除圆
                this.value = "删除矩形";
                flag = 1;
        } else if(flag == 1) {
                cvt.clearRect(9,9,102,102); //擦除矩形
                this.value = "恢复图形";
                flag = 2;
        } else {
                this.value = "删除圆形";
                draw(); //重绘图案
                flag = 0;
        }
}
//按钮2单击事件
document.getElementById('marq').onclick = function(){
        if(this.value == '继续运动'){
                start = setInterval(marq, 10); // 令小球运动
                this.value = '停止运动';
        } else {
                clearInterval(start); //令小球停止运动
                this.value = '继续运动';
        }
}

//绘制图形函数
function draw(){
        cvs.style.cssText = 'background: #000';
        cvt.strokeStyle = 'red';
        cvt.strokeRect(10,10,100,100);
        cvt.fillStyle = 'olive';
        cvt.arc(60, 60, 50, 0, 2*Math.PI);
        cvt.fill();
}
//小球运动函数
function marq(){
        cvt.beginPath();
        cvt.clearRect(dx-rr, dy-rr, rr*2, rr*2);
        cvt.fillStyle = "silver";
        dy = dy + speed;
        if(dy >= cvs.clientHeight - rr*2 || dy <= 20) speed = -speed;
        cvt.arc(dx, dy, rr, 0, 2*Math.PI);
        cvt.fill();
}

</script>

马黑黑 发表于 2022-3-9 09:55

本帖最后由 马黑黑 于 2022-3-9 09:57 编辑

一楼完整代码:

<div style="text-align:center">
        <canvas id="canvas" width="400" height="300"></canvas><br><br>
        <input id="del" type="button" value="删除圆形" />
        <input id="marq" type="button" value="停止运动" />
</div>

<script>

var flag = 0; //删除操作标识
var cvs = document.getElementById('canvas'); //画布句柄
var cvt = cvs.getContext('2d'); //画布句柄
var rr = 10; // 小球半径
var dx = cvs.clientWidth/2; //小球水平位置
var dy = 20; //小球垂直起始位置
var speed = 1; //小球行进速度

draw(); //绘制左上角图案
var start = setInterval(marq,10); //小球运动

//按钮1单击事件
document.getElementById('del').onclick = function(){
        if(flag == 0) {
                cvt.clearRect(10,10,100,100); //擦除圆
                this.value = "删除矩形";
                flag = 1;
        } else if(flag == 1) {
                cvt.clearRect(9,9,102,102); //擦除矩形
                this.value = "恢复图形";
                flag = 2;
        } else {
                this.value = "删除圆形";
                draw(); //重绘图案
                flag = 0;
        }
}
//按钮2单击事件
document.getElementById('marq').onclick = function(){
        if(this.value == '继续运动'){
                start = setInterval(marq, 10); // 令小球运动
                this.value = '停止运动';
        } else {
                clearInterval(start); //令小球停止运动
                this.value = '继续运动';
        }
}

//绘制图形函数
function draw(){
        cvs.style.cssText = 'background: #000';
        cvt.strokeStyle = 'red';
        cvt.strokeRect(10,10,100,100);
        cvt.fillStyle = 'olive';
        cvt.arc(60, 60, 50, 0, 2*Math.PI);
        cvt.fill();
}
//小球运动函数
function marq(){
        cvt.beginPath();
        cvt.clearRect(dx-rr, dy-rr, rr*2, rr*2);
        cvt.fillStyle = "silver";
        dy = dy + speed;
        if(dy >= cvs.clientHeight - rr*2 || dy <= 20) speed = -speed;
        cvt.arc(dx, dy, rr, 0, 2*Math.PI);
        cvt.fill();
}

</script>

马黑黑 发表于 2022-3-9 10:15

本帖最后由 马黑黑 于 2022-3-9 10:17 编辑

关于圆形图案的删除:

在画布里,圆是以圆心{xy}坐标和半径作为占位依据的,比如下面,我们绘制一个圆形图案——

context.arc(100, 80, 50, 0, 2*Math.PI);

——这是在画布的 {100, 80} 处画一个半径为 50 个像素的圆。那么,我们能不能这么删除它呢?

context.clearRect(100, 80, 50*2, 50*2);

显然不行,因为 clearRect 是擦除矩形区域,它的 {xy} 坐标值对应的是矩形区域的左上角,而上句使用的是圆心 {xy} 坐标值,这样只能擦除圆的1/4即右下部分。

让我们来分析一下 clearRect 应从何下手。圆心距离水平方向左边一个半径单位是对应矩形区域的 x 坐标值,距离垂直方向上方一个半径单位是对应矩形区域的 y 坐标值,因此将被擦除的矩形区域应往左、上分别移动一个半径单位:

x = 圆心-半径
y = 圆心-半径

修正上面的擦除例句:

context.clearRect(100 - 50, 80 - 50, 50*2, 50*2);

简写为:

context.clearRect(50, 30, 100, 100);

这样就能完美将圆擦除。

马黑黑 发表于 2022-3-9 10:16

本帖最后由 马黑黑 于 2022-3-9 10:36 编辑

关于圆形运动:

圆形运动与之前演示的矩形运动原理上是差不多的,不同的有两点,非常关键。

其一:圆的擦除(具体思路和方法请查阅上楼)。画布实现动画的方法之一是不断擦除、重绘,圆和矩形不同,不太好定它的占位区域,而如果擦除不干净,运动路线会有残留。

其二:运动的圆形(弧形)必须在绘制前声明路径,语句为:

context.beginPath();

无需参数。

一楼小球运动演示中,精准擦除上一个圆,再绘制下一个圆,所以不会破坏画布上的其他元素。如果不需要保留画布上的其他内容,有一个更简单的方法,无需用到擦除语句和设定路径语句,就是重设画布高度或宽度(有一个就可以了),因为只要重设画布高宽,画布就会自动清空,我们只管在新位置绘图圆形即可。重置方法(假设画布操作句柄为 canvas):

canvas.clientWidth = canvas.clientWidth;
canvas.clientHeight = canvas.clientHeight;

我们其实并没有改变画布的高宽,它们依然等于它们自己。上面两句中只需要一句就能发生作用,画布上的一切都会被擦除干净。

红影 发表于 2022-3-9 10:17

这个好复杂啊,不但有删除动作,还有按钮。

马黑黑 发表于 2022-3-9 10:18

红影 发表于 2022-3-9 10:17
这个好复杂啊,不但有删除动作,还有按钮。

嗯,不是很简单,不过代码并不算多,几十行而已

红影 发表于 2022-3-9 10:32

var dx = cvs.clientWidth/2; 这个也可以设置成数值的吧。

包括var rr = 10,为什么不直接使用数据,而忽视要设置这样一个代数?

马黑黑 发表于 2022-3-9 10:36

红影 发表于 2022-3-9 10:32
var dx = cvs.clientWidth/2; 这个也可以设置成数值的吧。

包括var rr = 10,为什么不直接使用数据,而 ...

dx 是圆心所在处,我想让小球放置在画布的水平方向的中央位置。直接用数值也行。
rr 是小球的半径变量,因为后面涉及到计算,也为了方便修改半径值以测试不同小球的运行状况,所以不直接用数值。

红影 发表于 2022-3-9 10:39

上下运动的时候,犹豫视觉差,感觉小球的上下貌似缺一块。

红影 发表于 2022-3-9 10:40

其实,包括上一个帖子,一直没看明白的是这两句:var speed = 1; //小球行进速度
dy = dy + speed; 这个缺是变幻垂直位置。为什么定位和速度可以直接加减、

马黑黑 发表于 2022-3-9 10:48

关于矩形的擦除:

我们之前演示过,用 fillRect 绘制出来的矩形,擦除它时所用到的参数是一样:

context.fillRect(10, 10, 200, 100); //绘制矩形
context.clearRect(10, 10, 200, 100); //擦除矩形

但如果我们用 strokeRect 方法绘制“空心”矩形:

context.strokeRect(10, 10, 200, 100);

这时,用相同的参数去擦除矩形,会发现擦除后仍有残留,矩形还在,只是变成了细边框的。

请仔细查阅二楼代码中擦除矩形的语句,我做了些参数数值调整。这里以上述矩形为例,给出精准擦除方法:

context.strokeRect(9, 9, 202, 102);

即,{xy} 各减去 1,w和h 各加 2。

原理:strokeRect 会加粗边框,在设定基础上加一个像素,所以擦除时定位要左移、上移各一个像素,矩形占位区域左、右、上、下均各扩宽一个像素,所以矩形的高度和宽度都实际增加了两个像素。

红影 发表于 2022-3-9 10:50

单击事件2比较难懂,尤其start = setInterval(marq, 10);,不明白为什么这个命令能让小球动。

单击事件1里flag = 不是很清楚呢,这个后面的值起什么作用、

马黑黑 发表于 2022-3-9 11:00

红影 发表于 2022-3-9 10:40
其实,包括上一个帖子,一直没看明白的是这两句:var speed = 1; //小球行进速度
dy = dy + speed; 这个缺 ...
dy在例中实际上是绘制小球的圆心位置,我们要在不同的位置绘制从而达到小球运动的效果,所以dy值要有变化。我们在画布上,在小球的当前位置的下一个或上一个像素的位置上重绘小球,这个重绘动作是连续不断地发生的。

dy的值原本是固定的,它要变化,或加或减,依据它所“行进”的距离,而speed是“速度”,例中值定为1,运行中speed值会变为 -1,并在 1 和 -1 之间来回变化,这样才能达成上下运动的效果。

dy初始值是 20,也就是我们第一次画小球是,小球的圆心是在画布上垂直位置 20px 处,dy+speed = 20+1,小球现在在 21px处,dy现在是21,接着又来,dy+speed = 21+1,我们在 22px 处又画圆……画到 cvs.clientHeight - rr*2 px 处时,speed变为 -1,dy+speed 则是 dy-1,往回走了。

就是说,通过将 speed 的值加到 dy 中来,令 dy 改变自己,也就是改变了圆心的垂直位置,达成运动的结果。

这样解释,应该明白了吧?

马黑黑 发表于 2022-3-9 11:09

红影 发表于 2022-3-9 10:50
单击事件2比较难懂,尤其start = setInterval(marq, 10);,不明白为什么这个命令能让小球动。

单击事件1 ...
这是 setInterval 定时器的运用,start 是定时器的操作句柄,关闭它时要用到它。

setInterval() 需要两个参数,第一个是JS语句,我在这里用 marq,marq是个自定义函数,setInterval调用这个函数;第二个参数是间隔时间毫秒数,例中是 10,意思是,每隔 10 毫秒运行一次 marq() 函数。

按钮1单击事件中的flag是一个操作标识符。因为按钮1有三个任务,擦除圆、擦除矩形、重绘图形,任务多,需要标明不同的功能,flag值不同,当次单击的功用就不一样。也可以像按钮2那样通过识别按钮的value值来判断操作功能,无需flag变量,都可以的。

红影 发表于 2022-3-9 12:08

马黑黑 发表于 2022-3-9 10:18
嗯,不是很简单,不过代码并不算多,几十行而已

要每句都看懂还挺不容易的。

红影 发表于 2022-3-9 12:10

马黑黑 发表于 2022-3-9 10:36
dx 是圆心所在处,我想让小球放置在画布的水平方向的中央位置。直接用数值也行。
rr 是小球的半径变量, ...

“为了方便修改半径值以测试不同小球的运行状况,所以不直接用数值。”
嗯嗯,明白了。{:4_187:}

红影 发表于 2022-3-9 12:25

马黑黑 发表于 2022-3-9 11:00
dy在例中实际上是绘制小球的圆心位置,我们要在不同的位置绘制从而达到小球运动的效果,所以dy值要有变化 ...

嗯嗯,整改过程明白了。前面只是对速度这个词的用法没吃透,实际那个应该是运动的每次变化的间隔,或者叫步进量,就更容易理解了。不知默认的每次擦除时间是多少。我看看去。。。。

红影 发表于 2022-3-9 12:29

马黑黑 发表于 2022-3-9 11:09
这是 setInterval 定时器的运用,start 是定时器的操作句柄,关闭它时要用到它。

setInterval() 需要 ...

有运动语句,还有按钮语句,容易搅在一起去。
按钮是要有鼠标动作才运行的吧,这个里面的时间间隔难道是鼠标点击后的时间?

‘每隔 10 毫秒运行一次 marq() 函数。’这个不是点击才有的么,怎么会自动运行?

红影 发表于 2022-3-9 12:34

马黑黑 发表于 2022-3-9 10:16
关于圆形运动:

圆形运动与之前演示的矩形运动原理上是差不多的,不同的有两点,非常关键。


“只要重设画布高宽,画布就会自动清空,我们只管在新位置绘图圆形即可”

如果画布上没有其他内容,也可以用这方法画出运动小球的吧。

马黑黑 发表于 2022-3-9 14:38

红影 发表于 2022-3-9 12:34
“只要重设画布高宽,画布就会自动清空,我们只管在新位置绘图圆形即可”

如果画布上没有其他内容,也 ...

可以
页: [1] 2 3 4
查看完整版本: HTML5画布:圆的精准删除及运动演示