【问题标题】:How to restore a canvas to status saved in another function?如何将画布恢复到保存在另一个函数中的状态?
【发布时间】:2013-05-24 05:24:50
【问题描述】:

我想在函数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()中,先将canvas恢复回来,再绘制剩余的动态部分。

【问题讨论】:

    标签: javascript canvas html5-canvas


    【解决方案1】:

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

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

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

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

    有两个主要选项:

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

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

    使用多个画布作为图层

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

    还可以使用 CSS 设置画布的 z-index。 z-index 属性指定元素的堆叠顺序。 z-index 值较低的项目排在 z-index 值较高的项目之后。

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

    演示

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

    Check the Fiddle

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

    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
    

    【讨论】:

    • 我真的很喜欢这个解决方案,但是你的 CSS 给我的其余布局带来了一些不必要的麻烦。如果你给#staticposition: relative,你可以摆脱#canvasesdiv的固定大小。结果应该有更少的 CSS 副作用。
    • @Max ,我同意你的看法。实际上#canvasesdiv 的大小在这种情况下并不重要,即使是#static position: absolute。该 div 仅用于设置画布的位置。但是如果我们想根据容器大小来缩放画布呢?看看这个小提琴:jsfiddle.net/gfcarv/sq5tg
    【解决方案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>
    

    【讨论】:

      【解决方案3】:

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

      但我认为您可能期望这些函数做一些不同于它们实际做的事情。

      这里是a fiddle with your code

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

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

      这里是an article that explains some of this

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

      【讨论】:

        猜你喜欢
        • 2011-11-05
        • 2013-12-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-16
        • 2016-03-03
        • 1970-01-01
        • 2014-10-24
        相关资源
        最近更新 更多