1

我想在函数 draw1() 中保存画布的状态,并在另一个函数draw2()恢复它。所以我写了以下代码。但它不起作用。

<canvas id="canvas" style="width:500px; height:500px;" height="500" width="500"></canvas>
ctx = document.querySelector('#canvas').getContext('2d');
function draw1(){
   ctx.save();
   ctx.fillRect(25,25,100,100);
}
function draw2(){
   ctx.restore();
}
draw1();
draw2();

我猜原因是状态保存在调用堆栈中。所以函数返回后,保存状态也被清除。

还有其他方法可以实现我的需求吗?

UPD:背景是我想实现一个简单的动画,大部分都是静态的。我希望使用setInterval()来执行绘图函数draw()。在draw()中,首先恢复画布并绘制剩余的动态部分。

4

3 回答 3

5

如果我理解正确,您只需要绘制一次静态对象,然后每帧绘制动画对象。

首先,您完全误解了saverestore方法,Michael Geary 向您展示了原因。markE还教您toDataURL随时拍摄画布快照并保存到图像对象中的方法。这是一个强大的功能,但不是您真正想要的简单动画。

那么,如何使用静态和动态对象创建动画呢?

如何使用静态和动态对象创建动画

有两个主要选项:

  1. 使用一个画布并在动画的每一帧中绘制所有对象(静态和动态),这对您来说可能不是最好的,因为您的大多数对象都是静态的。
  2. 有一个用于静态对象的画布和另一个用于动态对象的画布。使用这种技术,您只需要绘制一次静态对象并忘记它(无需“恢复”您的画布),我们在单独的画布中执行动画(每帧绘制动态对象)。

我认为最适合您的是选项 2。好的,但是我们如何设置这些画布?

使用多个画布作为图层

div使用 CSS 将所有画布设置为父标签内 (0,0) 的绝对位置。

还使用 CSS 设置画布的 z-index。z-index 属性指定元素的堆叠顺序。具有较低 z-index 值的项目位于具有较高 z-index 值的项目之后。

现在我们正确定义了画布,让我们开始吧!

演示

我制作了一个 jsFiddle 来向您展示如何完成所需的动画。

检查小提琴

以及该小提琴中使用的代码:

HTML:

<div id="canvasesdiv">
    <canvas id="static" width=400 height=400>This text is displayed if your browser does not support HTML5 Canvas</canvas>
    <canvas id="dynamic" width=400 height=400>This text is displayed if your browser does not support HTML5 Canvas</canvas>
</div>

CSS:

#canvasesdiv {
    position:relative;
    width:400px;
    height:300px;
}
#static {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 1;
}
#dynamic {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 2;
}

Javascript:

// static canvas
var static = document.getElementById("static");
var staticCtx = static.getContext("2d");

// dynamic canvas
var dynamic = document.getElementById("dynamic");
var dynamicCtx = dynamic.getContext("2d");

// animation status
var FPS = 30;
var INTERVAL = 1000 / FPS;

// our background
var myStaticObject = {
    x: 0,
    y: 0,
    width: static.width,
    height: static.height,
    draw: function () {
        staticCtx.fillStyle = "rgb(100, 100, 0)";
        staticCtx.fillRect(0, 0, static.width, static.height);
    }
};

// our bouncing rectangle
var myDynamicObject = {
    x: 30,
    y: 30,
    width: 50,
    height: 50,
    gravity: 0.98,
    elasticity: 0.90,
    friction: 0.1,
    velX: 10,
    velY: 0,
    bouncingY: false,
    bouncingX: false,
    draw: function () {   // example of dynamic animation code
        // clear the last draw of this object
        dynamicCtx.clearRect(this.x - 1, this.y - 1, this.width + 2, this.height + 2);            
        // compute gravity
        this.velY += this.gravity;
        // bounce Y
        if (!this.bouncingY && this.y >= dynamic.height - this.height) {
            this.bouncingY = true;
            this.y = dynamic.height - this.height;
            this.velY = -(this.velY * this.elasticity);
        } else {
            this.bouncingY = false;
        }
        // bounce X
        if (!this.bouncingX && (this.x >= dynamic.width - this.width) || this.x <= 0) {
            this.bouncingX = true;
            this.x = (this.x < 0 ? 0 : dynamic.width - this.width);
            this.velX = -(this.velX * this.elasticity);
        } else {
            this.bouncingX = false;
        }
        // compute new position
        this.x += this.velX;
        this.y += this.velY;            
        // render the object
        dynamicCtx.fillStyle = "rgb(150, 100, 170)";
        dynamicCtx.fillRect(this.x, this.y, this.width, this.height);
    }
};

function drawStatic() {
    myStaticObject.draw();
    // you can add more static objects and draw here
}

function drawDynamic() {        
    myDynamicObject.draw();
    // you can add more dynamic objects and draw here
}

function animate() {
    setInterval(function () {
        // only need to redraw dynamic objects
        drawDynamic();
    }, INTERVAL);
}

drawStatic(); // draw the static objects
animate(); // entry point for animated (dynamic) objects
于 2013-05-24T10:36:04.923 回答
2

您可以使用 canvas.toDataURL() 保存和重新加载画布上的像素

这是保存:

dataURL=canvas.toDataURL();

这是重新加载:

var image=new Image();
image.onload=function(){
    ctx.drawImage(image,0,0);
}
image.src=dataURL;

如果您需要保存上下文属性(fillStyle 等),则必须将它们保存在一个对象中,并在重新加载像素时将它们重新加载到上下文中。

如果您需要保存变换,则必须创建一个变换矩阵(6 个数字的数组)然后您需要通过操作变换矩阵来跟踪您所做的每个变换。请参阅此博客文章:http: //blog.safaribooksonline.com/2012/04/26/html5-canvas-games-tracking-transformation-matrices/

这是代码和小提琴:http: //jsfiddle.net/m1erickson/btmLE/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>

<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    ctx.fillRect(25,25,100,100);

    var dataURL;

    $("#save").click(function(){
        dataURL=canvas.toDataURL();
        ctx.clearRect(0,0,canvas.width,canvas.height);
    });

    $("#reload").click(function(){
        var image=new Image();
        image.onload=function(){
            ctx.drawImage(image,0,0);
        }
        image.src=dataURL;
    });

}); // end $(function(){});
</script>

</head>

<body>
    <canvas id="canvas" width=300 height=300></canvas><br>
    <button id="save">Save</button>
    <button id="reload">Reload</button>
</body>
</html>
于 2013-05-24T06:00:45.203 回答
1

不,.save()不要.restore()将画布状态保存在 JavaScript 调用堆栈中。从 JavaScript 函数返回不会影响该状态 - 它完全保存在该世界之外,在画布本身中。

但我认为你可能期望这些函数做一些它们实际做的事情之外的事情。

这是您的代码的小提琴

.fillRect()正如调用所期望的那样,它有一个黑色矩形。

您是否认为.restore()调用会使黑色矩形消失?这不是函数的作用。它不会将画布位图恢复到之前的状态,只会恢复其他画布设置,例如剪切区域、笔触和填充样式等。

是一篇解释其中一些内容的文章

如果你想保存实际的位图,你需要使用其他方法来做到这一点,也许使用.getImageDataHD().setImageDataHD()- 我不确定什么是最好的。

于 2013-05-24T05:33:17.993 回答