2011-03-11 94 views
2

我有一個模擬平鋪圖像的畫布元素表。我只需要填充視口中當前可見的那些圖塊。這裏是我使用的代碼將圖像加載到多個畫布時出現問題

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"); 
     } 
    } 
}; 

但問題是隻加載了該範圍的最後一個圖塊。同樣的事情,但以這種方式實現的是工作的罰款:在可見光範圍內是如何計算不應該的問題,因爲我在Firebug檢查,兩種方法的瓷磚呈現

$("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"); 
     } 
}); 

差異的正確選擇和實際平鋪圖像是從服務器下載的,但是對於第一種方法,只顯示最後一個圖塊,而對於第二個圖塊,所有圖塊都被正確渲染。

在此先感謝您的任何建議。

+0

更多upvotes for Maksym! ;) –

回答

2

的原因差別主要在這裏:

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

,做的是創建一個封閉,並將其分配給load事件的圖像。關閉關閉contextimage變量(和其他幾個)。關鍵是它有一個對這些變量的持久引用,而不是創建函數時它們的值的副本。由於上述代碼處於循環中,因此所創建的所有函數(閉包)都將引用與循環創建的最後一個函數相同的contextimage   —。

,因爲在該版本中,關閉關閉過,這並不能改變一個變量,它不會在each版本發生,一個contextimage由調用創建你傳遞迭代器功能each

更多有關閉的位置:Closures are not complicated

也許稍微題外話,也許不是:這可能是更清楚,如果你var小號都在不同的地方。 var is sometimes misunderstood.尤其是而不是與其他語言(例如C,C++,Java和C#)中的變量聲明相同,部分原因是JavaScript沒有塊級作用域(僅限於函數級作用域和全局範圍)。

這是JavaScript解釋器是如何看待你的第一個版本:

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;,發生在逐步執行函數的代碼。

(另外,題外話:沒有必要爲for環路的閉合};;但你要一個分配到onload處理程序後,我做了這兩個MODS的aboev以及。)

編寫代碼的方式解釋會看到它,現在(對我來說,反正)它更清晰,每個你分配給onload的倒閉將參照相同contextimage變量,所以他們都會看到最後一個。

如果你想使用非each版本,你只需要稍微改變它如此接近過自己的contextimage副本onload關閉。這看起來是這樣的:

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); 
    }; 
} 

現在由createOnloadHandler函數創建關閉關閉了ctimg,不contextimage。每個閉包都有自己的副本(傳遞給創建閉包的createOnloadHandler調用的副本)。

+0

非常感謝!它真的幫助了 –

+0

@Maksym:好交易,無後顧之憂。 –

+0

順便說一下,我閱讀了關於關閉的博客文章。它確實清除了我的理解。再次感謝! –