Mrz 16

Um eine Webseite attraktiv für den Benutzer zu machen bietet sich der Einbau von kleinen „Gimmicks“ an. Hierbei gilt es allerdings ein paar Dinge zu beachten. Zum einen sollte der Benutzer nicht zu sehr vom eigentlichen Inhalt der Webseite abgelenkt werden, zum anderen sollte die Webseite beim Fehlen des entsprechenden Gimmicks nicht eingeschränkt benutzbar sein, beispielsweise durch ein fehlendes Menü, oder ähnliches. Sprich, auch ohne JavaScript und Flash sollte die Seite weiterhin alle gewünschten Informationen bieten. Eine weitere „Gefahr“ solcher Spielereien ist, dass sie bei längerem verweilen auf der Webseite irgendwann nerven oder im schlimmsten Fall die Performance der Seite in die Knie gehen lassen. Sowas ist sehr unschön, mir aber trotzdem des Öfteren bei Flash-Games und solchen Sachen aufgefallen. Hier war es dann zu viel des Guten und aus der kleinen Spielerei am Rande wurde ein Nervtöter.

Ich habe mich (bzw. der Designer sich) bei meinem aktuellen Projekt mit dem Thema „Modelleisenbahn“ auch gefragt, was wohl auf der einen Seite ein kleiner optischer und spielerischer Leckerbissen ist, auf der anderen Seite aber auch nicht zu sehr ins Gewicht fällt. Was liegt hier also näher als eine „fahrende“ Eisenbahn?! Gesagt – getan: Im Geschäft wurden ein paar Fotos der Gleise, Loks und Waggons von oben aufgenommen und nach ein wenig Freistellungsarbeit vom Grafiker (Danke Johannes!) ging es auch schon an die Programmierung mit MooTools. Und genau die möchte ich hier kurz vorstellen.

Im wesentlichen besteht die ganze „Anwendung“ (ja, ist ein bisschen viel gesagt) aus zwei einfachen Klassen. Erstens dem „TrainSimulator“, der sich um die Züge und den „Fahrplan“ kümmert und zweitens einem „Train“, also dem Zug an sich. Dieser besteht einfach aus mehreren Grafiken von eben einer Lok und mehreren Wagen.

Zuerst also zum einfachen Teil, dem Train. Die Klasse besteht aus nur vier Methoden und zwei Membern. Da wäre als erstes der Container, ein einfaches HTML-Element, welches im Konstruktor erzeugt wird und zweitens einer Konstanten für einen y-Offset (später mehr). Der bereits erwähnte Konstruktor wird mit zwei Parametern aufgerufen, einer Grafikadresse der Lok und einem x-Offset. Der ist nötig, weil nicht alle Grafiken die gleiche Breite haben, die Fahrzeuge aber später genau mittig auf dem Gleis fahren sollen. Außer der Erzeugung des Containers samt Lok passiert hier nichts.

initialize: function(locomotive, leftOffset) {
	this.trainContainer = new Element('div').adopt(new Element('img', {
		'src': locomotive,
		'styles': {
			'position': 'relative',
			'left': leftOffset
		}
	}));
}

Mit der Methode „addWaggon“ könne beliebig viele Waggons angehängt werden, die Parameter sind wieder die Adresse der Grafik und ein x-Offset. Hier kommt außerdem der oben erwähnte y-Offset (Klassen-„Konstante“) zum Einsatz, der die Waggons gegeneinander verschiebt und damit die in den Grafiken vorhandenen Kupplungen übereinander legt. Die restlichen beiden Methoden „getWaggonCount“ und „getContainer“ erklären sich denke ich von selbst.

/**
 * Adds a new waggon to this train
 * @param {String} waggon image location
 * @param {Integer} leftOffset pixel offset
 */
addWaggon: function(waggon, leftOffset) {
	var waggonEl = new Element('img', {
		'src': waggon,
		'styles': {
			'position': 'relative',
			'top': (this.getWaggonCount() + 1) * this.waggonOffset * -1,
			'left': leftOffset
		}
	});
	this.trainContainer.adopt(waggonEl);
},

/**
 * Returns the amount of waggons in this train
 */
getWaggonCount: function() {
	return this.trainContainer.getChildren().length - 1;
},

/**
 * Returns the surrounding container element
 */
getContainer: function() {
	return this.trainContainer;
}

Doch nun zum spannenden Teil, dem TrainSimulator. Dieser bietet zu allererst einige Optionen im Konstruktor an, mit welchen man Dauer und Abstand der einzelnen Fahrten genauso bestimmen kann, wie die minimale Wagenanzahl und den Animationstyp. Es folgen die Definition der Loks und Wagen sowie deren unterschiedliche x-Offsets, je nach Breite der Grafik.

options: {
	targetContainer: 'page',
	minimumDuration: 15000,
	minimumDelay: 10000,
	minimumWaggons: 4,
	transition: 'linear'
},

/**
 * All locomotives need unique names and are represented by an image
 */
locomotives: {
	'steam1': ['fileadmin/sys/images/fahrzeuge/dampflok.png', 0],
	'diesel1': ['fileadmin/sys/images/fahrzeuge/diesel-blau.png', -3],
	'diesel2': ['fileadmin/sys/images/fahrzeuge/diesel-rot.png', -5],
	'steam2': ['fileadmin/sys/images/fahrzeuge/dampflok-lang.png', 3],
	'diesel3': ['fileadmin/sys/images/fahrzeuge/diesel-lang.png', 5],
	'electric1': ['fileadmin/sys/images/fahrzeuge/elok.png', 4]
},

/**
 * All waggons need unique names and are represented by an image and xoffset
 */
waggons: {
	'fasswagen': ['fileadmin/sys/images/fahrzeuge/fasswagen.png', 3],
	'gueter-geschlossen': ['fileadmin/sys/images/fahrzeuge/gueterwagen-geschlossen.png', 0],
	'gueter-schuett': ['fileadmin/sys/images/fahrzeuge/gueterwagen-schuettgut.png', 5],
	'autos': ['fileadmin/sys/images/fahrzeuge/autotransporter.png', 4],
	'kohle': ['fileadmin/sys/images/fahrzeuge/kohlewagen.png', 5],
	'kesselwagen': ['fileadmin/sys/images/fahrzeuge/tankwagen.png', 1]
}

Im Konstruktor wird nun der Container erzeugt, der über dem Gleis liegt und in welchem die Züge später Fahren. Außerdem werden die Loks und Wagen in Hashes initialisiert, um später leichter darauf zugreifen zu können. Ganz am Ende wird der erste Zug „gestartet“. Die „nextTrain“-Methode prüft zuerst ob es schon einen Zug gibt und entfernt ihn aus dem DOM und dem Speicher falls dem so ist. Anschließend wird ein neuer Zug konstruiert und das verzögerte Startsignal gegeben (delay).

/**
 * Initializes the locomotives, waggons and the track.
 * Also starts the trainbuilding process
 */
initialize: function(options) {
	this.setOptions(options);
	this.locomotives = new Hash(this.locomotives);
	this.waggons = new Hash(this.waggons);
	this.track = new Element('div', {
		styles: {
			'position': 'absolute',
			'top': 0,
			'left': '287px',
			'width': '73px',
			'overflow': 'hidden',
			'height': $(this.options.targetContainer).getCoordinates().height.toInt() - 40
		}
	}).injectInside($(this.options.targetContainer));
	this.nextTrain();
},

/**
 * Resets the track and delays the start of the next train.
 */
nextTrain: function() {
	if($defined(this.currentTrain)) {
		this.currentTrain.getContainer().dispose();
		delete this.currentTrain;
	}
	this.currentTrain = this.buildRandomTrain();
	var delay = Math.ceil(Math.random() * 10000 + this.options.minimumDelay);
	this.startRolling.delay(delay, this);
}

In „buildRandomTrain“ wird nun zufällig eine Lok und die Anzahl der minimalen plus einer zufälligen Anzahl (zwischen 0 und 3) Waggons gewählt und zu einem Zug zusammengefügt. Die Hilfsmethode „getRandomElement“ ermittelt dabei aus einem gegebenen Hash einen zufälligen aus und liefert ihn zurück. Die (übersprungene) Methode „startRolling“ startet einzig und allein die Animation und ruft am Ende ebendieser einfach erneut „nextTrain“ auf, womit der nächste Zug „bereit“ gemacht wird.

/**
 * Starts the locomotive engines...
 */
startRolling: function() {
	this.currentTrain.getContainer().setStyle('margin-top', this.track.getSize().y).injectInside(this.track).set('tween', {
		duration: Math.ceil(Math.random() * 10000 + this.options.minimumDuration),
		'onComplete': this.nextTrain.bind(this),
		'transition': this.options.transition
	}).tween('margin-top', this.currentTrain.getContainer().getCoordinates().height * -1);
},

/**
 * Creates a random train from possible locomotives and waggons
 */
buildRandomTrain: function() {
	var locomotive = this.getRandomElement(this.locomotives);
	var train = new Train( locomotive[0], locomotive[1] );
	for(var i = 0; i < Math.ceil(Math.random() * 3 + this.options.minimumWaggons); i++) {
		var waggon = this.getRandomElement(this.waggons)
		train.addWaggon( waggon[0], waggon[1] );
	}
	return train;
},

/**
 * Returns random element from given Hash
 * @param {Hash} sourceHash
 */
getRandomElement: function(sourceHash) {
	var random = sourceHash.getKeys().getRandom();
	return sourceHash.get(random);
}

Alles in allem also eine sehr übersichtliche und einfache Konstruktion welche (zumindest meiner Meinung nach) genau den Zweck des witzigen Hinguckers erfüllt und zudem themenspezifisch sehr gut in diese Seite passt. Für Kommentare und Anregunden zur Erweiterung/Verbesserung bin ich dankbar, also bitte gebt Eure Kommentare ab!

Die erste „Idee“ zur Verbesserung gibt es schon: Ein Steuerelement (z.B. ein Signal) um dieses Feature an- und abzuschalten. Vielleicht wird es also in Zukunft noch die Möglichkeit zum (de-)aktivieren geben.

Einen Kommentar schreiben