Problème preloader JavaScript

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour, je souhaite créer un preloader de ressources en JavaScript permettant de précharger Images, JSON, et sons et pouvoir y accéder. Cependant je rencontre des problèmes qui me paraissent impossible à résoudre:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
function Loader(arr, fn) {
    this.requiredFiles = arr; // liste des fichiers requis 
    this.loadedFiles = []; // liste des fichiers téléchargés
    this.files = {}; // objet stoquant les fichiers téléchargés, permet de les rendre accessible 
    this.fn = fn; //fonction de callback s'executtant dès que tout est téléchargé
}

Loader.prototype.load = function () {
    var i = 0;
    while (i < this.requiredFiles.length) {
        i++;

        switch (this.requiredFiles[i].type) {
        case "image":

            var img = new Image();
            //dès que l'image est chargée
            img.addEventListener('load', function (e) {
                this.loadedFiles.push(img); // dis que le fichier est chargé
                this.files[this.requiredFiles[i].key] = img; //rend le fichier accessible sous sa clé, via getFile(key)
                //Si tout est chargé, le callback s'execute
                if (this.requiredFiles.length === this.loadedFiles.length) {
                    this.fn();
                }

            }.bind(this), false);
            img.src = this.requiredFiles[i].file; // déclaration de l'image

            break;

        case "json":

            var xhr = new XMLHttpRequest();
            xhr.open('GET', this.requiredFiles[i].file);
            //dès que le json est chargé
            xhr.addEventListener('readystatechange', function () {
                if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                    this.loadedFiles.push(this.requiredFiles[i].file); //dis que le fichier est chargé
                    var jsonfile = JSON.parse(xhr.responseText); //parse le JSON en objet JS
                    this.files[this.requiredFiles[i].key] = jsonfile; //rend le fichier accessible sous sa clé via getFile(key)
                    //Si tout est chargé, le callback s'execute
                    if (this.requiredFiles.length === this.loadedFiles.length) {
                        this.fn();
                    }
                }
            }.bind(this), false);

            xhr.send();

            break;

        case "sound":
            // à remplir
            break;
        }
    }
}
Loader.prototype.getFile = function (key) {
        return this.files[key]; //renvoi le fichier en fonction de sa clé
    }
    //retourne en pourcentage les fichiers déjà chargés
Loader.prototype.getDownloadState = function () {
    return this.loadedFiles.length / this.requiredFiles.length * 100;
}
//déclaration des fichiers à charger, de leur clé et leur type
var myfiles = new Loader(
    [
        {
            file: 'jacky.json',
            key: 'jacky',
            type: 'json'
        },
        {
            file: 'jeanmi.json',
            key: 'jeanmi',
            type: 'json'
        },
        {
            file: '/images/tileset.png',
            key: 'tileset',
            type: 'image'
        },
        {
            file: '/images/characters.png',
            key: 'characters',
            type: 'image'
        }
    ],
    function () {
        alert("Tout est chargé!"); //Callback
    }
);
myfiles.load(); //on lance le chargement

Le problème se situe au niveau des évènements: Quand un fichier est chargé, on le rend accessible en le poussant dans files {} grâce à l'itérateur i: this.files[this.requiredFiles[i].key] = jsonfile; Mais quand le fichier est finalement chargé et que le code dans addEventListener s’exécute, la boucle a déjà avancé et i ne correspond plus au bon fichier. J'ai donc des

1
this.requiredFiles[i] is undefinied

Quelqu'un a une idée pour éviter ce fâcheux problème ? Merci d'avance :)

Édité par jerkoco

+0 -0

Le problème que tu rencontres est très courant et peut se résumer en :

1
2
3
for (var i = 0; i < 10; ++i) {
  setTimeout(() => console.log(i), 1000);
}

Avec ce code, tu vas te retrouver avec 10 fois le nombre 10 dans ta console.

Pour résoudre ce problème, il faut capturer la valeur de i. Il y a plusieurs méthodes pour le faire, mais je pense que la plus simple à comprendre est celle-ci :

1
2
3
for (var i = 0; i < 10; ++i) {
  ((i) => setTimeout(() => console.log(i), 1000))(i);
}

Au lieu d'exécuter le corps de la boucle directement, on créé une lambda que l'on appelle directement. Ça créé un nouvel environnement qui contient une copie de i, ce qui nous permet de conserver sa valeur.

Il est aussi possible d'utiliser let à la place de var. let déclare une variable locale à la boucle et chaque itération aura une variable différente pour i :

1
2
3
for (let i = 0; i < 10; ++i) {
  setTimeout(() => console.log(i), 1000);
}

Édité par Berdes

+0 -0
Auteur du sujet

Merci beaucoup Berdes, j'ai essayé de faire la solution 1 avec

1
2
(function(i) {
})(i);

Mais ça ne marche pas.. je n'ai pas d'erreur mais il ne se passe rien, pas de requêtes dans l'onglet réseau de la console, rien… :( J'ai mis le code sur github au cas où tu voudrais clone le code pour tester: https://github.com/raaac/preloader Je te remercie du temps consacré :)

Édité par jerkoco

+0 -0
Auteur du sujet

Je pourrais juste faire un compteur, mais là ça me permet de bien voir quel fichiers sont chargées et lesquels ne le sont pas, pour débuguer c'est plus simple. Sinon j'ai changé mis le .length dans la boucle et comme j'avais encore une erreur j'ai du mettre }.bind(this))(i);, et tout marche :) Je te remercie vraiment pour ton aide ! à bientôt !

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte