JavaScript - définition de la propriété sur les objets en fonction de la charge de l'image, la propriété non définie une fois en dehors de la fonction
-
04-10-2019 - |
Question
Parfois, JavaScript ne me fait pas sens, considérez le code suivant qui génère une mosaïque de photos basée sur x / y tuiles. Je suis en train de définir une propriété .done true une fois chaque image mosaïque a été téléchargé, mais il est toujours faux pour une raison quelconque, ce que je fais mal?
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);
}
Maintenant, pour une raison quelconque la propriété .Done
sur l'objet tile
est toujours faux, même si elle est explicitement être définie sur true à l'intérieur tileImage.onload = function()
. Et je peux vous assurer que cette fonction ne soit appelée parce que je l'ai placé un appel alert()
à l'intérieur. En ce moment, il est juste coincé dans une boucle infinie appelant tryDisplayMosaic()
constamment.
Aussi, si je place une boucle juste avant tryDisplayMosaic()
est appelé, et dans cet ensemble I boucle .Done = true
, alors la propriété .Done
est vrai et alert('All images downloaded');
sera appelé.
La solution
La variable « tuile » dans la boucle supérieure est partagée par chacun des fonctions « onload » vous créez; la même variable unique, autrement dit. Essayez ceci, de donner à chacun sa propre variable:
tileImage.onload = (function(myTile) {
return function()
{
// Do something with this tile Image
myTile.Done = true;
};
})(tile);
Pourquoi est-ce ainsi? Parce que contrairement à C ou Java, déclarant « tuile » dans la boucle comme cela ne pas en font scope au bloc de déclaration du corps de la boucle. La seule chose qui vous donne la portée en Javascript est un corps de la fonction.
Autres conseils
Ce que je vois est que vous créez des fermetures à l'intérieur des boucles, qui est une commune erreur . Pour éviter cela, vous pouvez créer une fermeture plus générale, en dehors de la boucle, qui gère tous les événements de charge et exécute une fonction après toutes les images sont chargées:
// 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;
}
}
Ce code est totalement différent de votre code, mais il est beaucoup plus jolie à mon avis. Il faut que vous définissez le nombre d'images que vous devez charger et une fonction qui doit être exécutée après que toutes les images sont chargées.
Note: Je n'ai pas testé, je ne suis pas sûr de cette partie .call(this)
, peut-être le forum pourrait aider si elle est incorrecte
J'ai testé mon code et il fonctionne très bien. De plus, je me suis amélioré, alors vous enregistrez un pointeur sur l'image chargée à l'intérieur du tableau de tileData
, consultez JSBin pour un exemple de travail.