将图像加载到多个画布的问题

时间:2011-03-11 05:08:22

标签: javascript html5 canvas

我有一个画布元素表,用于模拟平铺图像。我只需要填充视口中当前可见的那些图块。这是我使用的代码

for (var ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) {
        for (var iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) {
            var canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "-    disp");
            var canvasDisp = canvasDispJq.get(0);
            var image = new Image();
            var context = canvasDisp.getContext("2d");
            image.onload = function() {
                context.drawImage(image, 0, 0);
            }
            image.src = self.baseURL + '/' + canvasDispJq.attr("id");
        }
    }
};

但问题是只加载范围中的最后一个图块。同样的事情,但以这种方式实施工作正常:

$("table#canvasTable-" + frameResId + " canvas.display").each(function() {
        var canvasDispJq = $(this);
        if ((canvasDispJq.position().top + self.tileSize + imagePosition.y) > 0 &&
                (canvasDispJq.position().left + self.tileSize + imagePosition.x) > 0 &&
                (canvasDispJq.position().top + imagePosition.y) < self.containerHeight   &&
                (canvasDispJq.position().left + imagePosition.x) < self.containerWidth)   {
            var canvasDisp = canvasDispJq.get(0);
            var image = new Image();
            var context = canvasRaw.getContext("2d");
            image.onload = function() {
                context.drawImage(image, 0, 0);
            }
            image.src = self.baseURL + '/' + canvasDispJq.attr("id");
        }
});

计算可见范围的方式的差异无关紧要,因为我检查了Firebug,并且两种方法都正确选择了要渲染的图块,并且从服务器下载了实际图块图像,但是对于第一种方法,仅显示最后一个图块,而对于第二个所有瓷砖都正确呈现。

提前感谢任何建议。

1 个答案:

答案 0 :(得分:2)

差异的原因主要在于:

var image = new Image();
var context = canvasDisp.getContext("2d");
image.onload = function() {
    context.drawImage(image, 0, 0);
}

这样做是创建一个闭包并将其分配给图像的load事件。闭包关闭contextimage变量(以及其他几个变量)。这里的关键是它对这些变量有一个持久引用,而不是在创建函数时它们的值的副本。由于上面的代码是循环的,所以创建的所有函数(闭包)都将引用相同的contextimage - 循环创建的最后一个。

each版本中不会发生这种情况,因为在该版本中,闭包会关闭一个不会更改的变量, one context和{{通过调用迭代器函数创建的1}},你传递给image

有关此处闭包的更多信息:Closures are not complicated

也许稍微偏离主题,也许不是:如果你的each位于不同的地方,这可能会更清楚。 var is sometimes misunderstood.特别是, 与其他语言(例如C,C ++,Java和C#)中的变量声明相同,部分原因是JavaScript没有块级范围(仅功能级范围和全局范围)。

这就是JavaScript解释器看到你的第一个版本的方式:

var

请注意,所有var ix, iy, canvasDispJq, canvasDisp, image, context; for (ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) { for (iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) { canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "- disp"); canvasDisp = canvasDispJq.get(0); image = new Image(); context = canvasDisp.getContext("2d"); image.onload = function() { context.drawImage(image, 0, 0); }; image.src = self.baseURL + '/' + canvasDispJq.attr("id"); } } } 语句都已移至顶部,因为这就是它们的处理方式。无论var出现在函数的哪个位置,它都会从函数的最开始生效,并且只发生一次。如果var有初始值设定项,则会成为var所在的赋值。所以var实际上是两个完全独立的东西,由解释器在不同的时间处理:var x = 2;,在其他任何事情发生之前进入函数时发生,var x,发生在哪里它出现在函数代码的逐步执行中。

(另外,偏离主题:在x = 2;循环的结束;之后不需要};但是在分配到{{1}后确实需要一个for我已经把这两个mod都做了aboev。)

按照解释器看到的方式编写代码,现在(无论如何),更明确的是,您分配给onload的每个闭包都将引用相同的 onloadcontext变量,因此他们都会看到最后的变量。

如果您想使用非image版本,则只需稍微更改一下,以便each闭包关闭自己的onloadcontext副本。看起来像这样:

image

现在,var ix, iy, canvasDispJq, canvasDisp, image, context; for (ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) { for (iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) { canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "- disp"); canvasDisp = canvasDispJq.get(0); image = new Image(); context = canvasDisp.getContext("2d"); image.onload = createOnloadHandler(context); image.src = self.baseURL + '/' + canvasDispJq.attr("id"); } } } function createOnloadHandler(ct, img) { return function() { context.drawImage(img, 0, 0); }; } 函数创建的闭包关闭了createOnloadHandlerct,而不是imgcontext。每个闭包都有自己的副本(传递给创建该闭包的image调用的副本。)