JavaScript - Impostare proprietà su oggetti in funzione del carico Immagine, proprietà non di una volta la funzione di fuori
-
04-10-2019 - |
Domanda
A volte JavaScript non ha senso per me, si consideri il seguente codice che genera un mosaico di foto sulla base di x / y piastrelle. Sto cercando di impostare una proprietà .done true una volta ogni immagine mosaico è stato scaricato, ma è sempre falsa per qualche ragione, che cosa sto sbagliando?
var tileData = [];
function generate()
{
var image = new Image();
image.onload = function()
{
// Build up the 'tileData' array with tile objects from this Image
for (var i = 0; i < tileData.length; i++)
{
var tile = tileData[i];
var tileImage = new Image();
tileImage.onload = function()
{
// Do something with this tile Image
tile.Done = true;
};
tileImage.src = tile.ImageUrl;
}
};
image.src = 'Penguins.jpg';
tryDisplayMosaic();
}
function tryDisplayMosaic()
{
setTimeout(function()
{
for (var i = 0; i < tileData.length; i++)
{
var tile = tileData[i];
if (!tile.Done)
{
tryDisplayMosaic();
return;
}
}
// If we get here then all the tiles have been downloaded
alert('All images downloaded');
}, 2000);
}
Ora, per qualche motivo la proprietà .Done
sull'oggetto tile
è sempre false, anche se è in modo esplicito viene impostata su true tileImage.onload = function()
all'interno. E posso assicurarvi che questa funzione venga chiamato perché ho messo una chiamata alert()
al suo interno. In questo momento è solo bloccato all'interno di un ciclo infinito chiamando tryDisplayMosaic()
costantemente.
Anche se ho posto un ciclo appena prima tryDisplayMosaic()
si chiama, e in quel set .Done = true
ciclo I, quindi proprietà .Done
è vero e alert('All images downloaded');
verrà chiamato.
Soluzione
La variabile "piastrella" nel ciclo superiore è condiviso da tutti una delle funzioni "onload" create; la stessa variabile singola, in altre parole. Prova questo, per dare a ciascuno la propria variabile:
tileImage.onload = (function(myTile) {
return function()
{
// Do something with this tile Image
myTile.Done = true;
};
})(tile);
Perché è così? Perché a differenza di C o Java, dichiarando "tile" all'interno del ciclo del genere non rendono ambito al blocco di istruzioni del corpo del ciclo. L'unica cosa che ti dà ambito in Javascript è un corpo della funzione.
Altri suggerimenti
Quello che vedo è che si crea all'interno di chiusure loop, che è un comune errore . Per evitare questo, si potrebbe creare una chiusura più generale, al di fuori del ciclo, che gestisce tutti gli eventi di carico ed esegue una funzione, dopo tutte le immagini vengono caricate:
// This array must be filled somewhere...
var tileData = [];
var ImageLoader = function(){
var numOfImages = 0;
var numLoaded = 0;
var callBack = function(){};
function imageLoaded(){
numLoaded++;
if(numLoaded===numOfImages){
// All images are loaded, now call the callback function
callBack.call(this);
}
}
function init(numberOfImages, fn){
numOfImages = numberOfImages;
callBack = fn;
}
return {
imageLoaded: imageLoaded,
init: init
};
}();
function tryDisplayMosaic(){
alert('All images downloaded');
}
function generate(){
// (1) Set the number of images that are to be loaded and (all tiles + penguin)
// (2) Set the function that must be called after all images are loaded
ImageLoader.init(tileData.length+1, tryDisplayMosaic);
// Load this Penguins image
var image = new Image();
image.src = 'Penguins.jpg';
image.onload = ImageLoader.imageLoaded;
// Go through each image and load it
for (var i=0; i<tileData.length; i++) {
image = new Image();
image.src = tileData[i].ImageUrl;
image.onload = ImageLoader.imageLoaded;
}
}
Questo codice è totalmente diverso dal codice, ma è una più carina molto a mio parere. Si richiede di impostare il numero di immagini che è necessario caricare e una funzione che deve essere eseguita dopo tutte le immagini sono caricate.
Nota: non l'ho provato, io non sono sicuro di questa parte .call(this)
, forse il forum potrebbe aiutare se non è corretto
Ho testato il mio codice e funziona benissimo. Inoltre ho migliorato, in modo da risparmiare un puntatore per l'immagine caricata all'interno della matrice tileData
, vedi JSBin per un esempio di lavoro.