ugrás a tartalomhoz

Javascript image preloader (oop?)

bld · 2011. Dec. 20. (K), 16.27
Sziasztok. Egy image preloadert szeretnék írni, a képeket pedig utána megjeleníteni egy canvas elemen. A célom az lenne, hogy legyen egy feltöltött object-em, amin belül így hivatkoznék a képekre pl: images.elso, images.masodik stb.

Innen hívom meg:

window.onload = function(){
    c = document.getElementById('canvas')
    ctx = c.getContext('2d');       
   
    var images = new imagesRepository();
   
    ctx.drawImage (images.elso, 0, 0);
    ctx.drawImage(images.masodik, 200, 200);
     
}
ez pedig az imagesRepository tartalma:

function imagesRepository(){
   
    //set all the images here!
    //TODO implement: get images from config file
    var imagesArray = ["elso.jpg", "masodik.jpg"];
    var images = new Array();   
   
    images = this.preload(imagesArray);
   
    this.elso = images[0];
    this.masodik = images[1];               
   
}

imagesRepository.prototype.preload = function (imagesArray){
    var imagesCollection = new Array();
    var imageCount = 0;
   
    for (var i in imagesArray){
        imagesCollection[i] = new Image();
        imagesCollection[i].src = "images/" + imagesArray[i];
        imagesCollection[i].onload = function() {
            imageCount++;
            if (imageCount >= imagesArray.length){                               
                return imagesCollection;
            }
        };         
    }         
}
Odáig műkodik a dolog, hogy 2x belefut az image onloadba, viszont nem tudom innen hogyan kéne visszaadni az imagesRepository()-nak a feltöltött tömböt, hogy aztán elvégezhessem az objektum feltöltését.

Még csak most próbálgatom a javascript oop-t szóval nézzétek el, ha valami gyermeteg hibát vétettem.
 
1

callback

MadBence · 2011. Dec. 20. (K), 16.51
A preload függvény nem fog semmivel visszatérni, mivel a return csak az onload eseményben van (amiben ha return van, az nem azt fogja csinálni, amit vársz). Ha a ciklusod után visszaadod a tömböd (imagesCollection), akkor azt már az imageRepository() el fogja érni. (nyilván az imageRepository futásakor még egyik kép sem fog betöltődni, mivel az megakasztaná a futást. ehelyett ugye aszinkron módon töltődnek be a képek). De nem csak ezekre kell figyelni! :-)

Úgy kéne megoldanod, hogy az onload eseményben, ha minden kép betöltött (itt ügyelni kellene amúgy az esetleges hibákra, mi van, ha nem tölt be a kép? Az imageCount mindig kisebb lesz a tömb méreténél!), akkor meghívsz valami callback függvényt.

window.onload = function(){  
    c = document.getElementById('canvas')  
    ctx = c.getContext('2d');         
     
    var images = new imagesRepository();  
    images.ready=function()
    {
        ctx.drawImage(images.elso, 0, 0);  //images helyett jobb lenne ekkor a this
        ctx.drawImage(images.masodik, 200, 200);  
    };
}
Az imageRepository meg így módosulhatna:

function imagesRepository(){  
     
    //set all the images here!  
    //TODO implement: get images from config file  
    var imagesArray = ["elso.jpg", "masodik.jpg"];  
    var images = new Array();     
     
    images = this.preload(imagesArray);  
     
    this.elso = images[0];  
    this.masodik = images[1];                 
     
}  
  
imagesRepository.prototype.preload = function (imagesArray){  
    var imagesCollection = new Array();  
    var imageCount = 0;  
    var that=this; //ez itt magic!
    for (var i in imagesArray){  
        imagesCollection[i] = new Image();  
        imagesCollection[i].src = "images/" + imagesArray[i];  
        imagesCollection[i].onload = imagesCollection[i].onerror = function() {  
            imageCount++;  
            if (imageCount >= imagesArray.length){                                 
                that.ready();  
            }  
        };
    }
    return imagesCollection;           
}  
Amúgy nem teljesen értem, miért akarsz mindenáron így nevesítve hivatkozni rájuk, később több képpel kezelhetetlen lesz!
2

Köszönöm szépen

bld · 2011. Dec. 20. (K), 17.32
Bemásoltam, működik.

Eddig PHP-ben OOP-ztem, nem jutott volna eszembe, hogy osztályon "kívül" definiáljak egy osztálymetódust, pl imageRepository() is egy külön js fileban van. Ez nekem így kicsit fura, de elfogadom, hogy itt igy a célszerűbb. (Bár kérdés, hogy ez mennyire teszi átláthatatlanná a kódot.)

A var that = this; is érdekes, ezt ki tudnád egy kicsit fejteni?

Kérdésedre a válasz: úgy gondoltam így könnyebb lesz később hivatkozni a képekre, egy egyszerű kis játékot szeretnék, ami elemekből épül fel, tehát egy img elemet többször szeretnék használni. Te milyen megoldást javasolsz?
3

A that azért kell, mert az

MadBence · 2011. Dec. 20. (K), 17.50
A that azért kell, mert az onload metódusban hiába használnád a this-t, az nem az a this lesz, amire te számítasz (nem próbáltam ki, de úgy tippelem maga a létrehozott image objektum lenne a this), ezért kell kvázi más néven is elérhetővé tenni a repót. (én ezt eddig mindig így csináltam, ha van erre valami kevésbé "hekkelős" módszer, azt szívesen meghallgatnám :))
A megoldást tovább lehetne fejlesztni, ha az Observer pattern-szerűen, a "ready" eseményre te csak függvényeket regisztrálnál be, tehát a ready meg van írva az imageRepository "közelében", és az addEventListener (ez csak példa név, node.js-ben tipikusan úgy van megoldva, hogy az on(esemeny, callback) hívással lehet feliratkozni egy eseményre), amit szintén valahol a "közelében" írsz meg, bárhol meghívható, így nem szórja szét az osztály/objektum kódját.

Azt hittem valami képgaléria szerűt csinálsz, úgy nyilván érdemesebb lett volna számmal indexelni. De így már van létjogosultsága. De (szerintem) akkor sem az imageRepository névterét kellene szennyezni vele, hanem létrehozni neki benne valami gyűjtőt, amibe lehet nyugodtan pakolni. De ez már tényleg csak gusztus dolga.
4

úgy tippelem maga a

bld · 2011. Dec. 20. (K), 18.11
úgy tippelem maga a létrehozott image objektum lenne a this


A tipped helyes :)
6

Ha eddig csak PHP-ben

Hidvégi Gábor · 2011. Dec. 20. (K), 18.46
Ha eddig csak PHP-ben használtál OOP-t, akkor javaslom, mindenek előtt járj utána a JS-beli OOP-nek, mert nagyon máshogy működik.
11

Szerintem egyáltalán nem

inf · 2011. Dec. 20. (K), 22.56
Szerintem egyáltalán nem működik máshogy, csak js alatt mások a módszerek, az alap logika viszont ugyanaz marad.
5

Ha már mindenképpen el akarod

inf · 2011. Dec. 20. (K), 18.18
Ha már mindenképpen el akarod nevezni őket, akkor én inkább Mappel oldanám meg, és nem a fájlnevekkel...

Function.create = function (methods) {
    var destination = (methods.hasOwnProperty('constructor') ? methods.constructor : function () {});
    return Function.override(destination, methods);
};

Function.extend = function (base, methods) {
    var destination = (methods.hasOwnProperty('constructor') ? methods.constructor : function () {
        this.parent.constructor.apply(this, arguments);
    });
    destination.prototype = new base();
    destination.prototype.parent = base.prototype;
    return Function.override(destination, methods);
};

Function.override = function (destination, methods) {
    for (var property in methods)
        if (methods.hasOwnProperty(property))
            destination.prototype[property] = methods[property];
    return destination;
};

Map = Function.create({
    constructor: function (values) {
        this.source={};
        this.count = 0;
        if (values)
            this.putAll(values);
    },
    putAll: function (values) {
        for (var key in values)
            if (values.hasOwnProperty(key))
                this.put(key, value);
    },
    put: function (key, value) {
        if (this.containsKey(key))
            return;
        this.source[key] = value;
        ++this.count;
    },
    get: function (key) {
        return this.source[key];
    },
    containsKey: function (key) {
        return this.source.hasOwnProperty(key);
    },
    remove: function (key) {
        if (!this.containsKey(key))
            return;
        delete(this.source[key]);
        --this.count;
    },
    size: function () {
        return this.count;
    },
    forEach: function (callBack) {
        for (var key in this.source)
            if (this.source.hasOwnProperty(key))
                callBack.call(this, key, value);
    },
    toObject: function () {
        return this.source;
    }
});


ImageRepository = Function.extend(Map, {
    constructor: function (images) {
        this.parent.constructor.call(this, images);
    },
    setDirectory: function (directory) {
        this.directory = directory;
    },
    preload: function (callBack) {
        this.complete = new Map();
        this.callBack = callBack;
        this.forEach(this.preloadImage);
    },
    preloadImage : function (name, path) {
        this.createPreloader(name);
        this.createAbsolutePath(path);
        this.runPreloaderOnAbsolutePath();
    },
    createPreloader : function (name) {
        var repository = this;
        this.preloader = new Image();
        this.preloader.onload = preloader.onerror = function () {
            repository.imagePreloadCompleteObserver(name, this);
        };
    },
    createAbsolutePath : function (path) {
        if (typeof(this.directory) == "string")
            this.absolutePath = path;
        else
            this.absolutePath = this.directory + "/" + path;
    },
    runPreloaderOnAbsolutePath: function () {
        this.preloader.src = this.absolutePath;
    },
    imagePreloadCompleteObserver: function (name, preloader) {
        this.complete.put(name, preloader);
        if (this.size() == this.complete.size())
            this.preloadCompleteObserver();
    },
    preloadCompleteObserver: function () {
        this.callBack.call(this.toObject());
    }
});


window.onload = function(){
    var canvasElement = document.getElementById('canvas')
    var canvasContext = canvasElement.getContext('2d');

    var images = new ImageRepository({
        first: "elso.jpg",
        second: "masodik.jpg"
    });
    images.setDirectory("images");
    images.preload(function () {
        canvasContext.drawImage (this.first, 0, 0);
        canvasContext.drawImage(this.second, 200, 200);
    });

};
(Nem debuggoltam, lehet, hogy a drawImage nem fogad el Image objektumot, csak url-t, nem ismerem ...)

Egyébként CSS-el is lehet preload-olni, ha nem akarsz javascriptet használni...
7

Egyébként CSS-el is lehet preload-olni

Pepita · 2011. Dec. 20. (K), 18.47
Egyébként CSS-el is lehet preload-olni, ha nem akarsz javascriptet használni...

Ez engem nagyon érdekelne! Megírnád?
8

CSS preload

MadBence · 2011. Dec. 20. (K), 18.52
Az itt nem fog működni, mert konkrétan a drawImage metódus ha olyankor fut le, amikor még nem töltött be a kép, akkor nem rajzol ki semmit. Tehát mindenképpen meg kell várni a betöltődést!
9

Okés. Nem nagyon követem a

inf · 2011. Dec. 20. (K), 19.03
Okés. Nem nagyon követem a kliens oldali dolgokat mostanában...
10

Én írtam egyet csak nem

Karvaly84 · 2011. Dec. 20. (K), 21.23
Én írtam egyet csak nem találom, de amíg meglesz leírom:

- Kell egy ImageLoader osztály.
- Átadsz neki egy tömböt a képek url címével, egy callback-ot onload, és onerror-ra.
- Konstruktorban végigmész a tömbön: majd new Image();, majd bepakolod egy tömmbe, pl.: this.buffer.push-t használva.
- Minden képhez beállítasz egy onload, és egy onerror callback-ot. Ha betöltődött egy kép az onload-ban megnézed hogy van é még letöltés alatt kép, ezt egy sima számlálóval megoldod, az onerror-t rádbízom, ha a képek betöltődtek a konstruktornak átadott callback-ot meghívod úgy, hogy átadod neki a buffert, majd innen mész tovább.
12

Az ImageLoader

Karvaly84 · 2011. Dec. 21. (Sze), 00.42
Közben újra írtam mert elhagytam:
index.html

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="ImageLoader.js"></script>
<script type="text/javascript">

var loader = new ImageLoader({
	requests : ["img-1.jpg", "img-2.jpg", "img-3.jpg"],
	onload : function(images) {
		var body, i;
		body = document.getElementsByTagName("body")[0];
		for (i = 0; i < images.length; i++) {
			body.appendChild(images[i]);
		}
	},
	onerror : function(imgs) {
		console.log(imgs);
	}
})

</script>
<title>ImageLoader</title>
</head>
<body>

</body>
</html>
ImageLoader.js

function ImageLoader(options) {
	if (options) {
		this.init(options);
		if (this.onload || this.onerror) this.load();
	}
}

ImageLoader.prototype.init = function(options) {
	if (options.requests) this.requests = options.requests;
	if (options.onload) this.onload = options.onload;
	if (options.onerror) this.onerror = options.onerror;
	return this;
};

ImageLoader.prototype.load = function() {
	var loader, onload, onerror, i, requests, img;
	loader = this;
	requests = this.requests;
	this.images = [];
	this.remains = requests ? requests.length : 0;
	this.errors = 0;
	if (!(requests)) return this;
	onload = function() {
		--loader.remains;
		ImageLoader.check(loader);
	};
	onerror = function() {
		--loader.remains;
		++loader.errors;
		ImageLoader.check(loader);
	};
	for (i = 0; i < requests.length; i++) {
		this.images.push(img = new Image());
		img.onload = onload;
		img.onerror = onerror;
		img.src = requests[i];
	}
};

ImageLoader.check = function(loader) {
	if (loader.remains === 0) {
		if (loader.errors === 0 && loader.onload) loader.onload(loader.images);
		if (loader.errors > 0 && loader.onerror) loader.onerror(loader.images);
	}
};
Nem teszteltem agyon, elvileg működik! Letöltheted tömörítve innen, vagy checkout-olhatsz innen.