清除画布矩形区域(但保留背景)

16
我正在尝试给一个圆形添加动画并将其水平移动,这个过程很顺利。但是在圆形移动时,我必须对它进行clearRect操作,以便它可以在水平方向上重新绘制自己。当我执行clearRect操作时,它会使背景出现白色框,因此实际上它将成为一个沿着圆形移动方向的白色水平线。

  1. 是否有一种方法可以在不使用clearRect的情况下清除圆形?
  2. 如果我必须在clearRect之后保持重新绘制背景,那么当该区域中有10个圆形运动时,画布将闪烁。

还有其他解决方法吗?

    function drawcircle() {
        clear();    

        context.beginPath();
        context.arc(X, Y, R, 0, 2*Math.PI, false);                  
        context.moveTo(X,Y);            
        context.lineWidth = 0.3;
        context.strokeStyle = "#999999"; 
        context.stroke();

        if (X > 200)
        {
            clearTimeout(t); //stop
        }
        else
        {
            //move in x dir
            X += dX;
            t = setTimeout(drawcircle, 50);
        }
    }

    function clear() {
        context.clearRect(X-R, Y-R, 2*R, 2*R);
    }

画布上只有圆形吗? - Simon Sarris
不是的,我开始只是用一些简单的东西,然后希望在引入更多圆形或其他形状时使其更通用。我还没有测试过,但也许你可以在它沿着画布移动时及时设置它的可见性?这只是一个想法。 - fes
4个回答

28

基础知识: HTML5 Canvas 作为一种非保留绘图模式的图形 API

首先,让我们来了解一下HTML5 Canvas的工作方式。就像使用干得很快的油彩在真实世界的画布上绘画一样,当你使用stroke()或者fill()或者drawImage()方法在画布上绘制时,颜料变成了画布的一部分。尽管你可能画了一个“圆”,并将其视为这样,但圆的像素完全替换了背景(或者在圆的边缘进行反锯齿处理时,与其混合并永久修改了它们)。如果您要求莫奈把画中的某个人向右移动一点,他会说什么?你不能移动这个圆,也不能擦除这个圆,更不能检测到圆的mouseover事件……因为这里没有,只有一个二维像素数组。

一些选项

  1. 如果您的背景是完全静态的,请通过CSS将其设置为canvas元素的背景图像。这将显示您所绘制内容的叠加层,但在清除画布时不会被清除。

  2. 如果您无法执行上述操作,则最好清除整个画布并在每个帧中重新绘制它。在我的测试中,清除和重新绘制画布的一部分所需的工作量除非重新绘制画布非常昂贵否则不值得努力。

    例如,请参见此测试:http://phrogz.net/tmp/image_move_sprites_canvas.html
    在Safari v5.0.4中,如果我每帧清除并重新绘制整个画布,我可以看到59.4帧/秒,如果我使用20个clearRect()调用和20个drawImage()调用来重新绘制每个帧中被污染的背景部分,则只能看到56.8fps。在这种情况下,聪明地跟踪小的脏区域反而变得慢。

作为另一种选择,可以使用像SVG或HTML这样的保留绘图系统。对于这些系统,每个元素都是独立的。您可以更改项目的位置,它将神奇地移动;浏览器会智能地以最有效的方式绘制更新。
您可以通过在同一HTML页面中创建和分层多个画布(使用CSS绝对定位和z-index),同时保留自定义画布绘制的功能来实现此操作。如此性能测试中所示,通过CSS移动20个sprites比尝试在单个画布上全部完成要快得多。
闪烁?
你写到: 如果我必须在clearRect后不断重绘背景,当有10个圆在那个区域移动时,画布将会闪烁。
这从未是我的经验。您能否提供一个小例子,展示这个您声称会出现“闪烁”的问题(请指定您在哪个操作系统、浏览器和版本上经历了这个问题)?以下是两位知名浏览器开发人员的评论,指出Firefox和Safari都不应该出现任何闪烁问题。

这真的很有帮助,我一直在实现所有画布相关的内容,却完全没有考虑到 HTML/CSS 方面,这是我的错误。关于闪烁问题,我认为因为我需要在不同的时间间隔内重新绘制画布,以便不同的物体在画布上移动,可能会导致闪烁,但这可能也不是必然的。如果我看到任何闪烁,我一定会回报的。谢谢。 - fes
我也注意到我的Penthium 4 Ubuntu系统上出现了闪烁问题。当快速在3种颜色之间切换时,问题最为普遍。创建一个简单的矩形,其中包含三种颜色(自然红色,在鼠标按下时变为绿色,在鼠标释放时变为蓝色),如果您快速点击,看起来就像是不同的水平条纹,由红色、绿色和/或蓝色组成。 - puk
@puk,你所描述的是屏幕撕裂,即缺少垂直同步。双(三、四)缓冲也无法解决此问题。它需要渲染API(Web浏览器)与您的显示器刷新率进行同步。另请参阅HTML5 Canvas中的撕裂。 - Phrogz

1

通过简单地将多个画布叠加在一起,实际上非常容易实现。您可以在位于后台的画布上绘制背景,并在位于前景的第二个画布上绘制圆形。(即在背景画布前面堆叠)

多个画布实际上是提高任何动画性能的最佳方法之一,其中最终图像的元素独立移动并不一定在每一帧中移动。这允许您避免在每一帧中重新绘制未移动的项目。但是,请记住的一件事是更改在不同画布上绘制的项目的相对深度(考虑z-index)现在需要在dom中重新排序实际的<canvas>元素。在实践中,这很少是2D游戏和动画的问题。


1
与所接受的答案不同,是的,您可以恢复以前的绘图状态;而与其他答案所暗示的不同,不需要额外的画布: CanvasRenderingContext2D API包括函数getImageData()putImageData()。创建背景图像后,将整个图像存储在变量const background = context.getImageData(x, y, width, height)中(一个简单的Uint8ClampedArray类型的RGBA位图),然后使用clearRect()或其他方法擦除画布后,通过将该变量相反地传递回来简单地还原背景图像:context.putImageData(x, y, background)

0

有两种方法可以减少闪烁,特别是当你有许多圆时。

一种是双缓冲,关于这个问题的简短问题,您可以查看: HTML5/Canvas是否支持双缓冲?

基本上,您在两个画布上绘制,并根据需要交换它们。

这将是首选选项,特别是每帧有许多更改,但我做到这一点的另一种方式是只需使用背景颜色在要擦除的圆上绘制,然后用正确的颜色绘制新圆。

唯一的问题是,有一小部分机会可能会留下一些尝试擦除的证据,因为对于某些形状,很难确切地绘制在顶部。

更新:

根据评论,您可以查看有关画布双缓冲的讨论:

HTML画布双缓冲帧速率问题

基本思路是跟踪当前位置的一切已绘制内容,然后在单独的画布上重新绘制一遍,再将其翻转,最后在新位置再次进行重新绘制,以确保图像看起来与预期完全一样。将它们快速地交换是一个简单的操作,唯一的问题是如果你在画布上放置事件处理程序,在这种情况下,请将它们放在包围画布的 div 或 span 上,以免丢失此信息。

如果您将画布上的圆从一个已经绘制了背景的区域移动到另一个区域,会发生什么情况?当圆进入这个新的背景区域时,它会开始在这个背景上制造出白色的方块,这看起来不太好。我想你需要调整背景来适应这种移动。让背景变成纯白色解决了所有的clearRect问题,但如果有一个透明的clearRect或者清除绘制的形状本身会更好。 - fes
如果您能在任何浏览器上生成双缓冲通过两个画布的HTML5画布示例内容,而不是减慢应用程序,我会非常感兴趣。据我所知,所有HTML5画布实现已经进行了双缓冲,因为JavaScript调用被排队/合并,只有当脚本将控制返回给浏览器时才会发生可见更新。我很无礼地投下反对票,因为我认为这是FUD建议。如果您能展示一个问题案例,我会投赞成票。 - Phrogz
@Phrogz - 仅仅因为画布实现是双缓冲并不意味着拥有两个画布,一个用于绘制,一个用于显示,然后翻转,不是一个不好的选择。 - James Black

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接