ek

Beiträge zum Thema JS, HTML, CSS & anderem Kram

"Backboned" · AJAX-powered WordPress-Theme · Screenshot

Um mir gelegentlich etwas Zerstreuung vom Lernen zu geben, habe ich einen lange gehegten Plan in die Tat umgesetzt: Ein AJAX-betriebenes WordPress-Theme mit Backbone.js zu bauen. Und zwar keine auf “Graceful Degradation” setzende Kompromisslösung. Alle Inhalte werden asynchron geladen und sind per Hashbang URIs ansteuerbar. Ein Blick in den Quellcode offenbart, was ich meine: Ein JSON-Objekt mit allen grundlegenden Daten, eine Handvoll jQuery-Templates und das HTML-Grundgerüst. Das war es an statischem Content - die Darstellung des Inhalts geschieht über Backbone.js.

Damit Suchmaschinen nicht außen vor bleiben und man sich nicht die Mühe machen muss, einen “Headless Browser” à la HtmlUnit auf seinem Server zum laufen bringen zu müssen, werden grundsätzlich alle Inhalte als GET-Anfrage mit dem Parameter “_escaped_fragment_” abgehandelt - die Ausgabe variiert dann je nachdem zwischen statischem HTML oder einem nackten JSON-Objekt. So ist sichergestellt, dass die Inhalte trotzdem indiziert werden können. Die einzigen, die in die Röhre schauen, sind Besucher ohne JavaScript.

Um diesen Workaround zu realisieren, war jedoch ein hohes Maß an Improvisation vonnöten. Mit WordPress-Bordmitteln habe ich es nicht geschafft, das Frontend-seitige URL-Routing von Backbone.js server-seitig abzubilden und entsprechend zu bearbeiten. Ich habe deshalb auf ein simples MVC-Pattern zurück gegriffen und in das eigentliche Theme eine Art Child-Theme integriert. Das ist insgesamt kein Ansatz der mir - besonders in meiner Umsetzung - gefällt. Des weiteren muss man für einen störungsfreien Betrieb des Themes das URL-Rewriting in den WordPress-Einstellungen deaktivieren.

Darüber hinaus bleibt anzumerken, dass das Theme insgesamt eher rudimentär ist. Ich würde von einem Produktiveinsatz abraten. Allerdings bin ich grundsätzlich von der Idee des Themes überzeugt und freue mich natürlich, wenn jemand sich ebenfalls dafür begeistern kann und daran weiterarbeitet. Gerne auch in Kollaboration mit mir. Zu tun gibt es unter anderem noch:

  1. die grundsätzliche Verbesserung des PHP-Codes (sicherer machen, besser in das WordPress-Environment integrieren,…)
  2. den Funktionsumfang erhöhen (Neueste Kommentare, Tags, Suchfunktion, Sidebar-Widgets(?),…)
  3. das JavaScript straffen (Performance, geschmeidigere GUI-Abläufe,…)

…um ein paar Aspekte zu nennen.

Ansonsten freue ich mich wie immer über Anregungen und Verbesserungsvorschläge - gerade bei einem Vorhaben dieser Größenordnung hat man als Entwickler nicht wirklich einen umfassenden Überblick.

PS: Wahrscheinlich werde ich das Teil zeitnah bei GitHub reinladen. Muss mich da aber erst noch anschlauen.

Update: Okay, die Geschichte ist jetzt auch auf GitHub - https://github.com/herschel666/Backboned. Viel Spaß.

Kleinere Beiträge zum Thema "Worauf man beim JavaScript schreiben achten sollte" gibt es mittlerweile ja wie Sand am Meer im Interweb. Zusätzlich hat Douglas Crockford mit seinem Buch "JavaScript: The Good Parts" eine umfassende Sammlung geschaffen, hinsichtlich der Fallstricke und Annehmlichkeiten, die JavaScript bietet.

Wer allerdings den Kauf des Buches scheut und die Mühen, die das Finden der vielen kleinen, guten Beiträge mit sich bringt, vermeiden möchte, dem sei der JavaScript Garden ans Herz gelegt. Dort hat Ivo Wetzel eine ansehnliche und stetig wachsende Dokumentation der "most quirky parts of the JavaScript programming language" geschaffen.

Ich habe den Garten bisher lediglich kurz überflogen, daher gibt es die Empfehlung hier nur mit Vorbehalt. Ich bin aber zuversichtlich, dass da ein Quell an nützlichen Informationen angelegt wurde. Also guckt mal vorbei.

Gefunden bei bb-applications

Backbone.js ist ein interessantes JavaScript-MVC, mit welchem ich mich seit etwa zwei Wochen beschäftige. Und nun möchte ich ein kleines Tutorial dazu präsentieren - wir bauen uns ein Merkliste. Das Konzept sieht wie folgt aus: Ständig laufen einem tolle Filme, Bücher und Spiele über den Weg, die man unbedingt noch sehen/lesen/spielen möchte, aber man merkt sie sich nie. Das ist der Punkt, wo die Merkliste ins Spiel kommt.

Bei der Programmierung habe ich mich stark an der Todo List Application orientiert, allerdings ist die Merkliste vom Funktionsumfang her schmaler, beinhaltet dafür aber einen Controller für das URL-Routing.

Aber nun zur Sache:

Als erstes benötigen wir den HTML-Teil.

HTML

<!doctype html>
<html lang="de">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	
	<title>Backbone.js-Tutorial - Merkliste</title>	
	<link rel="stylesheet" href="style.css" />
	
</head>

<body>

	<ul id="nav"></ul>
	<input type="text" placeholder="Gib einen Titel ein&hellip;" id="list_input" />
	<ul id="list"></ul>
	
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
	<script src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.6/underscore-min.js"></script>
	<script src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.3.3/backbone-min.js"></script>
	<script src="js/backbone-localstorage.js"></script>
	<script src="js/list.min.js"></script>
	
	<script type="text/template" id="list-item-template">
		<strong><%= title %></strong>
		<span class="delete_item">x</span>
	</script>
	
	<script type="text/template" id="nav-template">
		<a href="#/category/<%= title %>"><%= title %></a>
	</script>
	
</body>
</html>

Für die Merklisten-App benötigen wir jQuery, Underscore.js, Backbone.js und die Backbone-Erweiterung Local-Storage, damit die Einträge im Browser gespeichert werden können. Des weiteren werden die Container für die Navigation und die Listeneinträge, sowie das Eingabefeld angelegt. Schlussendlich brauchen wir noch zwei Templates - eins für die Navigation, eins für die Liste.

Kommen wir nun zum JavaScript-Teil. Als erstes benötigen wir hier unser Model:

JavaScript

window.List = Backbone.Model.extend();

Als nächstes erstellen wir zwei Collections, eine für die Navigation, eine für die Listeneinträge:

JavaScript

window.NavCollection = Backbone.Collection.extend({
	model : List
});
	
window.ListCollection = Backbone.Collection.extend({				
		
	model : List,				
		
	localStorage : new Store('List'),
		
	getByCategory : function ( category )
	{
		return this.filter( function (item)
		{
			return item.get('category') == category;
		});
	}
});

In der Collection für die Listen-Einträge wird der Local Storage angemeldet und eine Funktion eingefügt, die es ermöglicht, die Einträge der Collection nach ihrer Kategorie zu filtern.

Kommen wir nun zum Controller der App:

JavaScript

window.ListController = Backbone.Controller.extend({
		
	_navModel : new NavCollection([
		{title : 'Filme'},
		{title : 'Buecher'},
		{title : 'Spiele'}
	]),
	_navViews : [],
	_categoryModel : new ListCollection,				
	_inputView : null,
		
	routes : {
		'' : 'init',
		'/category/:category' : 'getItems',
	},
		
	initialize : function ()
	{			
		this._navModel.each( function ( item, i )
		{
			this._navViews[i] = new NavigationView({
				model : item
			});
		}, this);
			
		Backbone.history.start();
	},
		
	init : function ()
	{
		window.location.hash = '/category/Filme';
	},
	
	getItems : function ( category )
	{			
		for ( view in this._navViews )
		{
			this._navViews[view]
				.render()
				.setClass();
		};
							
		if ( this._inputView == null )
		{		
			this._inputView = new ListInputView({
				model : this._categoryModel,
				category : category
			});
		}
		else
		{
			this._inputView.options.category = category;
			this._inputView.model.trigger('refresh');
		}
	}
		
});

Als erstes werden - jeweils für die Navigation und die Liste - neue Instanzen der zugehörigen Collection erstellt und Platzhalter für die jeweiligen Views angemeldet. Danach werden die relevanten Pfade mit Funktionen verknüpft. In diesem Fall wird die Funktion init() ausgeführt, wenn kein Hash vorhanden ist, und die Funktion getItems(), wenn ein Kategorie-Hash vorhanden ist.

Als nächstes folgt die initialize-Funktion, welche als erstes beim Aufrufen des Controllers ausgeführt wird. Dabei wird das Navigation-Model mit den nötigen Einträgen versehen, danach für jeden Eintrag der Navigation-Collection eine View-Instanz erstellt und im _navView-Array gespeichert, sowie die Backbone.history-Funktion gestartet.

Als nächstes wird die init-Funktion definiert. Diese sorgt einfach nur dafür, dass die Kategorie "Filme" gewählt wird, indem der entsprechende Kategorie-Hash gesetzt wird. Die Kategorie ist hierbei willkürlich von mir gewählt.

Danach folgt die Definition der getItems-Funktion. In dieser werden als erstes die Views für die Navigationspunkte ge-rendert. Daraufhin folgt entweder die Initialisierung des Views für die Listeneinträge, oder - falls dies schon geschehen ist - das Überschreiben der aktuellen, dem View übergebenen Kategorie und das Neu-Aufbauen der Liste mit den entsprechenden Einträgen. Dabei kommt die Filter-Funktion der Listen-Collection zum Einsatz.

Nachdem nun Model, Collection und Controller vorhanden sind, geht es an die Views. Davon benötigen wir drei - einen für das Eingabefeld, einen für die Liste und einen für die Navigation. Fangen wir mit dem Eingabefeld an:

JavaScript

window.ListInputView = Backbone.View.extend({
	
	el : $('#list_input'),
	
	list : $('#list'),
	
	events : {
		'keypress' : 'createListItem'
	},
	
	initialize : function ()
	{
		_.bindAll(this, 'addListItem', 'addAllListItems');
		
		this.model.bind('add', this.addListItem);
		this.model.bind('refresh', this.addAllListItems);
		
		this.model.fetch();
	},
	
	createListItem : function (e)
	{
		if ( e.keyCode == 13 )
		{			
			this.model.create({
				category : this.options.category,
				title : this.el.val()
			});
			
			this.el.val('');
			this.el.blur();
		}
	},
	
	addListItem : function ( item )
	{					
		var view = new ListItemView({model : item});
		
		!view.model.length && this.list.append( view.render().el );
	},
	
	addAllListItems : function ()
	{
		this.list.empty();
		
		_.each(this.model.getByCategory(this.options.category), function(item)
		{
			this.addListItem(item);
		}, this);
	}
	
});

Der ListInputView nimmt die Eingabe entgegen, erstellt eine neue Instanz des Listen-Eintrag-View und fügt füllt die Liste mit Einträgen. Die Funktion createListItem() erstellt einen neuen Eintrag in der Collection, wenn das Eingabefeld abgefeuert wurde. Über die Angabe this.model.bind('add', this.addListItem); in der initialize-Funktion wird gesorgt, dass daraufhin die Funktion addListItem() aufgerufen wird, die für den neuen Eintrag in der Collection eine Instant des Listen-Eintrag-View erstellt und diesem den Befehl render() mit auf den Weg gibt. Über die Funktion addAllListItems() wird einerseits sichergestellt, dass die Liste geleert wird, bevor neue Einträge nach einem Kategorie-Wechsel reingladen werden, und andererseits, bei Übergabe einer Collection mit mehreren einträgen, für jeden Eintrag die Funktion addListItem() ausgeführt wird.

Als nächstes kommen wir zum Listen-Eintrag-View:

JavaScript

window.ListItemView = Backbone.View.extend({
		
	tagName : 'li',
		
	className : 'list_item',
		
	tmpl : _.template( $('#list-item-template').html() ),
		
	events : {
		'click .delete_item' : 'removeItem'
	},
		
	render : function ()
	{		
		$(this.el).html( this.tmpl( this.model.toJSON() ));
			
		return this;
	},
		
	removeItem : function ()
	{
		this.model.destroy();
		$(this.el).fadeOut( function()
		{
			$(this).remove();
		});
	}
		
});

Hier wird das Element vom Standard (div) auf li gesetzt, die gewünschte CSS-Klasse gesetzt, das Template für den Eintrag angemeldet, ein Klick-Event mit der Funktion removeItem() verknüpft und anschließend die Funktionen render() und removeItem() definiert. Erstere fügt die Daten des Collection-Eintrags in das Template ein, letztere löscht den View und den dazugehörigen Collection-Eintrag.

Schlussendlich benötigen wir noch einen View für die Navigation:

JavaScript

window.NavigationView = Backbone.View.extend({
		
	tagName : 'li',
		
	tmpl : _.template( $('#nav-template').html() ),
		
	hash : function ()
	{
		return window.location.hash.replace('#/category/', '');
	},
		
	render : function ()
	{							
		$('#nav').append($(this.el).html( this.tmpl( this.model.toJSON() ) ));
			
		return this;
	},
		
	setClass : function ()
	{
		var curHash = this.hash();
			
		this.el.className = ( curHash == $(this.el).find('a').text() ) ? 'current' : '';
	}
		
});

Neu und interessant ist hier die Funktion setClass(). Diese sorgt nach dem Rendern der Navigation, dass der aktuelle Reiter die Klasse "current" bekommt.

Um die Listen-App nun zum Laufen zu bringen, erstellen wir eine neue Instanz des Controllers:

JavaScript

var listApp = new ListController();

Der ganze Spaß wird in eine anonyme jQuery-Funktion geschrieben, damit die App gestartet wird, sobald das DOM geladen ist.

Das war es auch schon. Ich hoffe, meine Erklärungen sind einigermaßen nachvollziehbar. Falls nicht, nutzt auf jeden Fall die Kommentar-Funktion. Des weiteren würde ich mich natürlich über Anregungen und Verbesserungsvorschläge freuen, da ich ja doch ein Neuling in Sachen Backbone.js bin. Ansonsten noch der Hinweis, dass bei Gefallen natürlich gerne regen Gebrauch von den unten stehenden Social-Media-Buttons gemacht werden kann.

Vielen Dank!

Anmerkung: Die Überschrift ist genau genommen etwas irreführend, da das PlugIn die Email-Adresse vielmehr entschleiert. Das aber nur am Rande erwähnt.

Ein altes Problem, für welches es mittlerweile zugegebenermaßen schon viele Lösungen gibt, ist das Veröffentlichen von Email-Adressen auf Websites. Schreibt man die richtige Adresse in den Quelltext, macht man seinen Besuchern das Leben leichter. Aber man arbeitet auch den Email-Spammern in die Hände, die das Netz nach Adressen durchforsten.

Verschleiert man seine Email-Adresse, indem man bspw. das @-Zeichen durch "(at)" ersetzt, hat man das Problem nicht. Nur müssen Besucher die Zeichen erst umständlich ersetzen, wenn sie eine Email an die Adresse schreiben wollen.

Meine Lösung des Problems ist eine Kombination aus Verschleierung der Adresse im Quelltext und ein Re-Build der Adresse per Javascript mithilfe eines jQuery-Plugins:

Javascript

(function($){
     $.fn.obfuscateEmail = function(at, point, addClass)
    {
        /* Regular Expressions fuer @ und . definieren
	 * falls die Argumente gesetzt sind
	 **/
					
        var at = at && new RegExp(at, 'g') || false,
            point = point && new RegExp(point, 'g') || false;
					
	/* Plugin-Funktion ausfuehren, falls
	 * Regular Expressions fuer @ und . definiert
	 * sind.
	 * Ansonsten nur 'this' zurueck geben.
	 **/
						
        return at && point && this.each(
		function()
		{
			/* Wenn 'addClass' auf 'true' gesetzt ist,
			 * dem Element die Klasse 'js' anhaengen
			 **/
							
			this.className = this.className + ( addClass && ' js' || '');
							
			/* Verschleierte Adresse anhand der Leer-
			 * zeichen in Array aufsplitten
			 **/
							
			var mailTo = this.innerHTML.split(' '),
			    i, mailToLen = mailTo.length,
			    address = [];
							
			/* Anhand der uebergebene @- und .-Werte
			 * jedes Element des mailTo-Arrays pruefen
			 * und ggf. Zeichen ersetzen.
			 * mailTo-Elemente in address-Array speichern.
			 **/
								
			for ( i=0; i<mailToLen; i++ )
			{
				address.push( point.test(mailTo[i]) ?
				'.' :
				at.test(mailTo[i]) ?
				'@' :
				mailTo[i] );
			}
							
			/* Inhalt des Elements mit mailto-Link
			 * und der unverschleieerten Adresse aus
			 * dem address-Array befuellen
			 **/
							
			this.innerHTML = '<a href="mailto:' + address.join('') + '">' + address.join('') + '</a>';
		}
        ) || this;
    };
})(jQuery);

Angewendet wird das PlugIn folgendermaßen:

Javascript
$(document).ready( function() {
	$('span.email').obfuscateEmail('(at)', '(punkt)', true);
});

Die ersten beiden Argumente der Funktion definieren, mit welchen Ausdrücken das @- und .-Zeichen der Email-Adressen verschleiert ist. Das dritte Argument ist ein Boolean-Wert und gibt an, ob den Adressen die Klasse 'js' angehängt werden soll, um sie unterschiedlich stylen zu können, je nachdem, ob der Besucher JavaScript aktiviert hat oder nicht. Das PlugIn arbeitet sich dann durch alle Adressen der Seite, hängt im Bedarfsfall die Klasse 'js' an und ersetzt die verschleierte Adresse durch die korrekte, inklusive mailto-Hyperlink zum einfachen Versenden im Email-Client bei Klicken der Adresse.

Die Adressen können folgendermaßen aussehen:

HTML
<span class="email">hansmustermann (at) domain (punkt) com</span>
<p class="email">foo <em>(punkt)</em> bar <em>(at)</em> domain <em>(punkt)</em> com</p>

Wichtig bei den Adressen ist, dass die einzelnen Elemente durch ein Leerzeichen getrennt sind, sonst funktioniert es nicht. Wie man in der zweiten Zeile sieht, können die verschleierten Symbole beliebig in HTML gefasst werden, um die Adresse für Besucher ohne JavaScript besser lesbar zu machen. Der Funktionalität des PlugIns tut dies kein Abbruch, solange die Leerzeichen vorhanden sind.

So einfach ist. Fragen, Anregungen, Verbesserungsvorschläge bitte in die Kommentare. Vielen Dank.

Heute möchte ich kurz zeigen, wie man mit etwas HTML, CSS und ein paar kleinen GIF-Grafiken eine pixelige Seiten-Navigation im 8Bit-Stil baut. Außerdem benutzen wir etwas jQuery-Magic, um der Navigation noch Fly-Out-Menus zu spendieren.

8Bit-Style-Navigation mit Fly-Out-Menus

Beginnen wir wie gewohnt mit dem HTML-Teil:

HTML
<div id="nav">
  <ul>
    <li class="top">
      <a href="index.html">
        <strong>Home</strong>
      </a>
      <div class="sub">
        <div>
          <ul>
            <li>
              <a href="#">Sub-Item 1</a>
            </li>
            <li>
              <a href="#">Sub-Item 2</a>
            </li>
            <li>
              <a href="#">Sub-Item 3</a>
            </li>
            <li>
              <a href="#">Sub-Item 4</a>
            </li>
            <li>
              <a href="#">Sub-Item 5</a>
            </li>
          </ul>
        </div>
      </div>
    </li>
    <li class="top">
      <a href="#">
        <strong>About</strong>
      </a>
      <div class="sub">
        <div>
          <ul>
            <li>
              <a href="#">One Sub-Item</a>
            </li>
            <li>
              <a href="#">Another Sub-Item</a>
            </li>
            <li>
              <a href="#">Still a Sub-Item</a>
            </li>
          </ul>
        </div>
      </div>
    </li>
    <li>
      <a href="#">
        <strong>Contact</strong>
      </a>
    </li>
  </ul>
</div>

Wie gewohnt eine ungeordnete Liste für die Haupt-Navigation und jeweils eine für die Sub-Navigationen. Um die charakteristischen Ecken hinzubekommen, müssen zwei Elemente ineinander verschachtelt und gegeneinander verschoben werden. Das macht den Quelltext Tag-intensiver. In meinen Augen jedoch noch in einem vertretbaren Rahmen und weit entfernt von klassischer "Diveritis".

Als nächstes kommen wir zum CSS:

CSS
@font-face {
  font-family: 'SilkscreenNormal';
  src: url('slkscr-webfont.eot');
  src: local('☺'), url('slkscr-webfont.woff') format('woff'), url('slkscr-webfont.ttf') format('truetype'), url('slkscr-webfont.svg#webfontUx1SMfhe') format('svg');
  font-weight: normal;
  font-style: normal;
}

ul {
  list-style: none;
}

body {
  background-color: #FFF;
  color: #333;
  font: normal 13px SilkscreenNormal, sans-serif;
}

#nav,
#nav > ul,
#nav > ul > li {
  float: left;
  display: inline;
}

#nav,
#nav > ul {
  width: auto;
  _width: 1%; /* IE6 Hack */
  height: 32px;
}

#nav {
  margin: 50px;
  position: relative;
  border-width: 2px 0;
  border-style: solid;
  border-color: #666;
}

#nav > ul {
  position: relative;
  left: -2px;
  margin-right: -4px;
  padding: 0 10px;
  border-width: 0 2px;
  border-style: solid;
  border-color: #666;
  background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP///73n/yH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
  *background: url('img/tile1.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
		
#nav > ul > li {
  _width: 1%; /* IE6 Hack */
  margin: 3px 5px;
  position: relative;
}
			
#nav > ul > li > a {
  display: block;
  position: relative;
  width: auto;
  height: 22px;
  border-width: 2px 0;
  border-style: solid;
  border-color: #999;
  background-color: #FFF;
}
				
#nav > ul > li > a:link,
#nav > ul > li > a:visited {
  color: #999;
  text-decoration: none;
  background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP/////fvSH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
  *background: url('img/tile2.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
				
#nav > ul > li > a:hover,
#nav > ul > li > a:focus,
#nav > ul > li > a:active {
  color: #666;
  background-image: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP/////MmSH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=');
  *background-image: url('img/tile3.gif'); /* IE6 and IE7 can't handle data uris */
				}
				
#nav > ul > li > a strong {
  display: block;
  position: relative;
  width: auto;
  height: 22px;
  padding: 0 10px;
  line-height: 22px;
  left: -2px;
  margin-right: -4px;
  border-width: 0 2px;
  border-style: solid;
  border-color: #999;
}
					
#nav > ul > li.top > a > strong {
  padding-left: 21px;
  background: url('data:image/gif;base64,R0lGODlhBgAEAIABAJmZmf///yH5BAEAAAEALAAAAAAGAAQAAAIHhI8WocuwCgA7') 5px center no-repeat;
  *background: url('img/arrow.gif') 5px center no-repeat; /* IE6 and IE7 can't handle data uris */
}
					
#nav > ul > li > a:hover,
#nav > ul > li > a:hover strong,
#nav > ul > li > a:focus,
#nav > ul > li > a:focus strong {
  border-color: #666;
}
				
#nav > ul > li.top > a:hover strong,
#nav > ul > li.top > a:focus strong {
  background-image: url('data:image/gif;base64,R0lGODlhBgAEAIABAGZmZv///yH5BAEAAAEALAAAAAAGAAQAAAIHhI8WocuwCgA7');
  *background-image: url('img/arrow_hv.gif'); /* IE6 and IE7 can't handle data uris */
}
				
#nav > ul > li > a:active {
  top: 1px;
}
				
.sub {
  position: absolute;
  width: auto;
  top: 24px;
  left: 0;
  padding-top: 9px;
  display: none;
}
				
.sub div {
  position: relative;
  border-top: 2px solid #666;
  border-bottom: 2px solid #666;
}
					
.sub div ul {
  position: relative;
  left: -2px;
  margin-right: -4px;
  border-left: 2px solid #666;
  border-right: 2px solid #666;
  background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP///+7u7iH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
  *background: url('img/tile4.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
						
.sub div ul li {
 border-top: 2px solid #666;
}
							
.sub div ul li:first-child {
  border-top: none;
}
						
.sub div ul li a {
  display: block;
  padding: 0 10px;
  line-height: 22px;
  font-size: 12px;
  white-space: nowrap;
}
								
.sub div ul li a:link,
.sub div ul li a:visited {
  color: #666;
}
								
.sub div ul li a:hover,´
.sub div ul li a:focus {
  background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP///93d3SH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
  *background: url('img/tile5.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
								
.sub div ul li a:active {
  background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP///8zMzCH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
  *background: url('img/tile6.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}

Als erstes binde ich den Pixel-Font "SilkScreen von Jason Kottke ein. Danach folgen die Angaben für die Navigation.

Interessant ist dabei, dass selbst das äußere Element <div id="nav"> die Angabe float: left hat, damit es sich der Breite des Inhalts anpasst. Das kann zu Layout-Problemen führen, weshalb man im praktischen Einsatz darauf achten muss, das Element direkt unterhalb der Navigation mit einem clear: left zu versehen.

Des weiteren kann man sehen, wie die charakteristischen Ecken zustande kommen: Das äußere Element hat jeweils unten und oben eine zwei-Pixel-starke border und das innere jeweils links und rechts. Das innere Element wird dann per left: -2px und margin-righ: -4px um jeweils zwei Pixel nach links und rechts aus dem umgebenden Element hinaus gezogen. Schon ist der gewünschte Effekt da.

Ebenfalls erwähnenswert sind die Grafiken. Da diese nur 4x4 Pixel bzw. 4x6 Pixel groß sind, lohnt es sich, sie in Form von Data URIs einzubinden und so unnötige HTTP Requests zu sparen. Blöderweise können IE6 und IE7 damit nicht umgehen, weshalb die richtigen Grafiken ebenfalls eingebunden werden müssen. Diese werden dann mithilfe des Star-Hack den beiden Browsern zugewiesen.

Bilder in Data URIs umwandeln könnt ihr übrigens mit diesem Online-Tool.

Zum Schluss noch etwas jQuery um die Fly-Out-Menu-Funktionalität zu realisieren:

JavaScript
$(document).ready( function() {
  $('#nav li.top').hover( function() {
    $(this).find('div').stop(true, true).fadeIn('slow');
  }, function() {
    $(this).find('div').stop(true, true).fadeOut('slow');
  });
});

Ich denke, das ist die spartanischste Lösung und bedarf keiner weiteren Erläuterung.

Das war es auch schon. Die Navigation ist beliebig per Copy&Paste erweiterbar, denkt nur daran, den li-Elementen, die eine Sub-Navigation beinhalten, die Klasse top zu verpassen, damit die Fly-Out-Menu-Funktionalität gewährleistet ist.

Ansonsten wünsche ich viel Spaß mit der Navigation. Bei Fragen bitte wie immer die Kommentar-Funktion nutzen. Und bei Gefallen fleißig via Twitter und Facebook verbreiten. Vielen Dank :-)

Der Like-Button von Facebook zum Einbinden auf externen Websites ist ja nach wie vor in aller Munde und da ich gerade auf einem anderen WordPress-Blog einen solchen Button per XFBML eingebunden habe, möchte ich hier kurz vorstellen, wie das geht.

Als erstes braucht ihr eure User-ID und eine Application-ID für die Seite, auf der ihr den Like-Button einbauen möchtet. Die User-ID bekommt ihr, indem ihr folgendes in die Adress-Zeile eures Browsers eingebt:

graph.facebook.com/euer.name

Vereinfacht gesagt: Geht auf euer Facebook-Profil und ersetzt das www in der Adresse durch "graph". Auf der dann erscheinenden Seite, findet ihr eure User-ID.

Die Application-ID wiederum könnt ihr generieren, indem ihr auf folgender Seite euren Blog eintragt:

http://developers.facebook.com/setup/

Nun sind die Grundvoraussetzungen erfüllt, der Like-Button kann eingebunden werden. Im folgenden Beispiel zeige ich, wie man das in der Einzelansicht eines Artikels macht. Dafür müssen wir im Kopf- und Fußbereich, sowie unterhalb des Artikels etwas Code eingeben.

Fangen wir mit dem Kopfbereich an:

HTML/PHP
<?php if ( is_single() ) : ?>
  <meta property="fb:admins" content="eure_user_id" />
  <meta property="fb:app_id" content="eure_app_id" />
  <meta property="og:site_name" content="<?php bloginfo('name'); ?>" />
  <meta property="og:title" content="<?php echo get_the_title(); ?>" />
  <meta property="og:type" content="article" />
<? endif; ?>

Diese fünf speziellen Facebook-Meta-Angaben müssen in der header.php innerhalb der head-Tags gesetzt werden. Sie verraten Facebook wem der Blog gehört, um welchen Blog es sich handelt, wie er heißt, welchen Titel die aktuelle Seite hat und um welchen Inhaltstyp es sich handelt. Natürlich müsst ihr eure_user_id und eure_app_id durch die entsprechende ID ersetzen.

Als nächstes binden wir den nötigen JavaScript-Kram im Fußbereich ein:

HTML/JavaScript
<?php if ( is_single() ) : ?>
  <div id="fb-root"></div>
  <script src="http://connect.facebook.net/en_US/all.js" type="text/javascript"></script>
  <script type="text/javascript">
    window.fbAsyncInit = function() {
      FB.init({appId: 'eure_app_id', status: true, cookie: true, xfbml: true});
    }
  </script>
<?php endif; ?>

Das wird in die footer.php direkt über dem abschließenden body-Tag eingefügt. eure_app_id ist auch hier entpsrechend zu ersetzen.

Nun muss der Button selbst noch auf der Artikelseite eingebunden werden:

HTML
<fb:like href="<?php the_permalink() ?>" layout="standard" show_faces="false" width="xyz" action="like" colorscheme="light" font="trebuchet ms"></fb:like>

Dies tragt ihr in der single.php da ein, wo der Button hin soll. Die Attribute könnt ihr in gewissem Maße euren Präferenzen anpassen. Hier erfahrt ihr, was möglich ist.

Hat man das alles eingetragen, werden auf der Einzelansicht eines Artikels die nötigen Meta-Angaben angezeigt, das entsprechende JavaScript für den Like-Button wird eingebunden und der Like-Button selbst erscheint ober- oder unterhalb des Artikels - je nachdem, wo ihr ihn eingefügt habt.

Sollte euch das alles zu tricky sein, könnt ihr entweder die iframe-Variante einbinden oder auf das WordPress-PlugIn von Bottomless zurückgreifen: Like.

Noch ein paar Worte zur Performance. Das Einbinden des Like-Button verlängert natürlich die Ladezeit eures Blogs. Allerdings sprechen wir hier von WordPress - hier ist es normal, dass auf jeder Seite drei zusätzliche JavaScript-Dateien und zwei zusätzliche Stylesheets eingebunden werden, weil irgendwo im Blog mal eine Lightbox aufgehen soll. Will heißen: Denkt darüber nach, ob ihr den Button einbauen wollt, aber bedenkt, dass euer System sowieso nicht Performance-optimiert ist.

Es sei denn, ihr seid bezüglich dieses Themas sensibilisiert und habt entsprechende Schritte unternommen, eurem Blog auf die Sprünge zu helfen.

Ansonsten wie immer: Bei Fragen schreibt einen Kommentar. Bei Gefallen bookmarkt den Artikel. Danke!

"Rising Curtain"-Effekt bei Input-Feldern mit jQuery

Hinweis:

Die CSS-Eigenschaft background-position-x bzw. background-position-y ist eine Erfindung von Microsoft und funktioniert leider nur im Internet Explorer und in Webkit-Browsern. Deshalb gibt es im jQuery-Skript eine Browser-Weiche. Im Firefox und im Opera verschwindet die Grafik einfach nur, der "Rising Curtain"-Effekt findet also nicht statt.

Kommen wir aber zum eigentlichen Tutorial:

Heute möchte ich zeigen, wie man input-Felder in Formularen mithilfe des "Rising Curtain"-Effekts und jQuery interessanter gestalten kann, wenn der Fokus auf ihnen liegt. Alles was man dafür braucht, ist eine kleine Hintergrundgrafik mit einem Farbverlauf und ein paar Zeilen jQuery. Als erstes aber wenden wir uns dem HTML-Teil zu:

HTML
<div>
   <span>
      <input type="text" id="input1" />
   </span>
   <label for="input1">Input 1</label>
</div>

Dieser bedarf wohl keiner großen Erklärung. Es gibt ein input-Feld plus dem dazugehörigen Label. Das input-Feld ist in span-Tags eingefasst, welche für den Rand sorgen.

Als nächstes der CSS-Teil:

CSS
div {
   height: 24px;
   margin-bottom: 10px;
   clear: left;
}
   
   span {
      height: 24px;
      float: left;
      display: inline;
      margin-right: 10px;
      border: 1px solid #999;
}

   span.active {
      border-color: #063050;
   }

      input {
         display: block;
         width: 200px;
         height: 18px;
         padding: 2px 4px;
         line-height: 18px;
         background: #FFF url('img/gradient.jpg') 0 0 repeat-x;
         border: 1px solid #FFF;
         color: #063050;
      }

   label {
      height: 24px;
      float: left;
      display: inline;
      line-height: 24px;
      cursor: pointer;
   }

span und label werden mit float: left nebeneinander angeordnet. Das input-Feld bekommt einen weißen Rand, damit der dunkle Hintergrund nicht an den dunklen Rand des span-Elements klatscht. Der Hintergrund wiederum stellt einen Farbverlauf von dunkelblau zu weiß dar, der mehr als doppelt so hoch wie das input-Feld ist.

Kommen wir nun zum jQuery-Teil:

JavaScript
jQuery(document).ready(function($) {
   $('input').focus( function() {
      $(this).parent().addClass('active');
      if ( jQuery.browser.webkit || jQuery.browser.msie ) {
         $(this).animate({'background-position-y' : '-28px'}, 500, 'linear');
      } else {
         $(this).animate({'background-position' : '0 -28px'}, 0, 'linear');
      }
   });
   $('input').blur( function() {
      $(this).parent().removeClass('active');
      if ( jQuery.browser.webkit || jQuery.browser.msie ) {
         $(this).animate({'background-position-y' : '0'}, 500, 'linear');
      } else {
         $(this).animate({'background-position' : '0 0'}, 0, 'linear');
      }
   });
});

Liegt der Fokus auf einem input-Feld, bekommt das umschließende span-Element die Klasse "active" zugewiesen und der Rand wird dunkelblau. Außerdem verschiebt sich der Hintergrund des input-Feldes nach oben mithilfe der animate()-Funktion von jQuery. Das ist der so genannte "Rising Curtain"-Effekt, durch den der Hintergrund des input-Feldes heller wird. Gesteuert wird das ganze über die CSS-Eigenschaft background-position-y.

So einfach ist das. Das ganze lässt sich natürlich auch bei einer textarea anwenden. Denkt aber daran, dass die Hintergrund-Grafik entsprechend groß sein muss. Auch ist es denkbar, die Hintergrund-Grafik ganz aus dem sichtbaren Bereich fahren zu lassen, so dass das input-Feld weiß wird. Der Kreativität sind kaum Grenzen gesetzt.

Das war es auch schon. Bookmark setzen oder dieses Tutorial via twitter durch den Äther jagen, wird wie immer gern gesehen. Bei Fragen und Anregungen steht die Kommentar-Funktion zur Verfügung. Nichts neues also.

Semi-transparente Navigation in WordPress mit CSS

Heute geht es um eine horizontale Reiternavigation in WordPress, deren inaktive Reiter dank CSS Browser-übergreifend semi-transparent sind. Dafür muss als erstes ein span-Element innerhalb der Listen-Elemente der Navigation eingefügt werden. Dies geschieht über folgenden PHP-Code-Schnipsel:

HTML/PHP
<div id="header">
   <ul>
      <li<?php if (is_home() || is_paged() ) echo " class=\"current_page_item\""; ?>>
         <span></span>
         <a href="<?php bloginfo( 'url' ); ?>" title="Back to Main    Page">Home</a>
      </li>
      <?php
         echo preg_replace('@\<li([^>]*)>\<a([^>]*)>(.*?)\<\/a>@i', '<li$1><span></span><a$2>$3</a>', wp_list_pages('echo=0&title_li=&depth=1'));
      ?>
   </ul>
   <div class="hr"><hr /></div>
</div>

Das span-Element bekommt in diesem Fall die Transparenz. Würde man die Transparenz auf das a-Element anwenden, würde auch die darin liegende Schrift transparent werden und das soll vermieden werden. Außerdem wird vor der Funktion zur Ausgabe der Seiten noch ein Listenelement mit dem Link zur Startseite eingesetzt. Der ist optional und kann auch entfernt werden, wenn man z.B. den Blog-Titel auf die Startseite verlinkt. Schlussendlich befindet sich unterhalb der Navigation noch ein von einem div-Element umschlossenes hr-Element. Unter diesem werden die inaktiven Listenelemente verschwinden.

Kommen wir nun zum CSS:

CSS
#header ul {
   height: 60px;
   position: relative;
   clear: left;
}

   #header ul li {
      width: auto;
      _width: 140px; /* IE6 needs a fixed width */
      height: 60px;
      margin-left: 20px;
      float: left;
      display: inline;
      position: relative;
   }

      #header ul li span {
         display: block;
         width: 100%;
         _width: 140px; /* IE6 needs a fixed width */
         height: 60px;
         position: absolute;
         top: 10px;
         left: 0;
         z-index: 1000;
         background-color: #FFF;
         background: -moz-linear-gradient( top, #c7e0ed, #fff );
         background: -webkit-gradient( linear, left top, left bottom, from(#c7e0ed), to(#fff) );
         opacity: 0.7;
         filter: alpha(opacity=70);
         -moz-opacity: 0.7;
         filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
         -khtml-opacity: 0.7;
         -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)";
      }

      #header ul li a:link,
      #header ul li a:visited {
         display: block;
         width: auto;
         height: 24px;
         padding: 13px 20px 23px 20px;
         line-height: 24px;
         position: relative;
         bottom: -10px;
         z-index: 2000;
         color: #0660cf;
         text-decoration: none;
         text-shadow: #FFF 0 1px 2px;
      }

      #header ul li a:hover,
      #header ul li.current_page_item a {
         background-color: #FFF;
         background: -moz-linear-gradient( top, #c7e0ed, #fff ); /* gradient for firefox */
         background: -webkit-gradient( linear, left top, left bottom, from(#c7e0ed), to(#fff) ); /* gradient for chrome and safari */
         position: relative;
         bottom: 0;
         _top: 0; /* special invite for IE6 to get goin' */
      }

      #header ul li a:hover {
         color: #c41604;
      }

      #header ul li a:active {
         color: #0059ce;
         position: relative;
         bottom: -1px;
         _top: 1px; /* special invite for IE6 to get goin' */
      }

.hr {
   height: 30px;
   background-color: #333;
   background: -moz-linear-gradient( top, #333, #555 ); /* gradient for firefox */
   background: -webkit-gradient( linear, left top, left bottom, from(#333), to(#555) ); /* gradient for chrome and safari */
   position: relative;
   z-index: 3000;
}

   .hr hr {
      display: none; /* hiding hr because of the IE. quel surprise! */
   }

Am meisten Platz nehmen hier die Angaben für die Transparenz ein. Das liegt daran, dass alle Eventualitäten - sprich "Browser" - berücksichtigt werden müssen. Dafür lässt sich diese Navigation aber auch mit reinem CSS und ohne PNG-Grafiken umsetzen, die ja im IE6 für Ärger sorgen würden.

Trotzdem funktioniert auch das hier nicht ohne dem ein oder anderen Mätzchen unserer Lieblings-Browser aus Redmond - den Internet Explorern. Der IE6 benötigt eine feste Breite für die Listenelemente. Hier ist also etwas Anpassung nötig, je nachdem wie lang die Beschriftung der Reiter ist. Der IE7 hingegen hat Probleme mit den Hover-Zuständen, so dass die Reiter hier nicht nach oben fahren und intransparent werden, wenn man mit dem Cursor rüberfährt. Das ist ärgerlich, schränkt jedoch die Benutzbarkeit der Seite nicht ein.

Des weiteren habe ich noch etwas CSS3 verwendet. Mit den aktuellen Versionen von Firefox, Safari und Chrome bekommt man deshalb einen Farbverlauf bei den Reitern serviert. In den gleichen Browsern sowie der aktuellen Version von Opera gibt es außerdem noch einen leichten Schlagschatten bei der Link-Beschriftung.

Das ist auch schon alles. Und natürlich ist diese Navigation nichts WordPress-spezifisches. In der Demo gibt es eine statische Variante, die in jeder anderen Seite auch verwendet werden kann.

Ansonsten mein übliches Mantra an dieser Stelle: Wenn ihr Fragen habt oder etwas nicht so funktioniert, wie es soll, nutzt die Kommentar-Funktion. Und wenn euch das Tutorial gefallen hat, würde ich mich freuen, wenn ihr es bookmarked oder twittert. Vielen Dank :-)

Hover-und-Spotlight-Effekt in Bildergalerie mit jQuery

Jede Bildergalerie gewinnt, wenn das Thumbnail-Bild unter dem Cursor durch einen Hover-Effekt hervorgehoben wird und der Nutzer weiß, was gerade Sache ist. Wenn die umliegenden Thumbnails dann auch noch durch einen Spotlight-Effekt abgedunkelt werden, ist es noch leichter für den Nutzer, sich auf das aktuelle Bild zu konzentrieren. Wie man das mit jQuery realisiert, zeige ich jetzt.

Als erstes der HTML-Teil:

HTML
<body class="js">
	<div class="wrap">
		<a href="path/to/image.html" class="image">
			<img src="path/to/image.jpg" alt="" />
		</a>
		<a href="path/to/image.html" class="image">
			<img src="path/to/image.jpg" alt="" />
		</a>
		<a href="path/to/image.html" class="image">
			<img src="path/to/image.jpg" alt="" />
		</a>
	</div>
</body>

Als nächstes der CSS-Teil:

CSS
.wrap {
	width: 321px;
	height: 321px;
	padding: 20px 0 0 20px;
	margin: 30px auto;
	clear: left;
}

	.image {
		width: 77px;
		height: 77px;
		padding: 5px;
		margin: 0 20px 20px 0;
		float: left;
		display: inline;
		position: relative;
		z-index: 10;
		background-color: #CCC;
	}
	
	.js .image:hover {
		background-color: #36F;
	}
	
		.image img {
			position: relative;
			z-index: 30;
			border-width: 1px;
			border-style: solid;
			border-color: #666 #999 #999 #666;
		}
		
		.layer1, .layer2 {
			display: block;
			position: absolute;
		}
		
		.layer1 {
			width: 87px;
			height: 87px;
			top: 0;
			left: 0;
			z-index: 20;
			background-color: #36F;
		}
		
		.layer2 {
			width: 77px;
			height: 77px;
			top: 5px;
			left: 5px;
			z-index: 40;
			background-color: #000;
		}

Kleine Anmerkung dazu: Wenn JavaScript im Browser aktiviert ist, wird die Klasse js vom body-Tag entfernt. Geschieht dies nicht, bekommt das a-Tag um die Thumbnails einen :hover-Effekt.

Schlussendlich der jQuery-Teil:

JavaScript
jQuery(document).ready(function($) {
	$('body').removeClass('js');
	var image = $('a.image');
	var html = '<span class=\"layer1\"></span><span class=\"layer2\"></span>';
	image.append(html).find('span').css('opacity', '0');
	image.each( function() {
		$(this).hover( function() {
			$('span.layer1', this).stop().fadeTo(800, 1);
			$(this).siblings().find('span.layer2').stop().fadeTo(800, 0.5);
		}, function() {
			$('span.layer1', this).stop().fadeTo(800, 0);
			$(this).siblings().find('span.layer2').stop().fadeTo(800, 0);
		});
	});
});

Als erstes wird die Klasse js vom body-Tag entfernt. Danach werden die beiden span-Tags eingefügt, die für den Hover- und den Spotlight-Effekt zuständig sind. Danach wird in einer each()-Schleife festgelegt, dass bei einem Hover die span-Tags erscheinen und entsprechend wieder verschwinden.

Das war es auch schon. Falls es Fragen oder Probleme gibt, möchte ich euch wie immer die Kommentar-Funktion nahe legen. Ansonsten freue ich mich natürlich, wenn ihr den Artikel bookmark't oder auf twitter verbreitet.

Aperture - Screenshot

Aperture ist eine Photo-Editing-Software von Apple™, welche die Möglichkeit bietet, Galerien zu erstellen und im HTML-Format zu exportieren. Diese Funktion kann ich aufgrund des generierten Quelltextes nicht empfehlen, nichtsdestominder ist sie sehr praktisch für Leute, die schnell eine Galerie veröffentlichen wollen, jedoch kein HTML können.

Ein weiteres Manko ist der Umstand, dass jedes Foto eine eigene Unterseite bekommt und man sich an diesen entlang hangelt. Heutzutage allerdings - möchte ich behaupten - ist eine Lightbox Standard, wenn es um das Betrachten von Fotos im Browser geht. Deshalb möchte ich zeigen, wie man mithilfe von jQuery und einem PlugIn diese Funktionalität relativ schnell nachrüsten kann.

Als erstes benötigen wir das PlugIn jQuery lightBox. Wir entpacken das Zip-File und legen den js-Ordner, den css-Ordner und den images-Ordner aus dem jquery-lightbox-0.5-Ordner in dem aus Aperture exportierten Galerie-Ordner ab.

Nun muss das PlugIn eingebunden werden und zwar innerhalb der <head>-Tags der index.html der Galerie. Als erstes die beiden JavaScript-Dateien und das Stylesheet:

HTML
<link rel="stylesheet" type="text/css" href="css/jquery.lightbox-0.5.css" media="screen" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.lightbox-0.5.pack.js"></script>

Mit folgendem JavaScript-Code aktivieren wir die Lightbox:

JavaScript
$(document).ready( function() {
	$('dd.imagecell a').lightBox();
});

Nun ergibt sich allerdings das Problem, dass das Lightbox-PlugIn darauf angewiesen ist, dass die Links um die Thumbnails der Fotos auf die große Version der selben verweisen. Dies tun sie in der Aperture-Galerie allerdings nicht. Vielmehr verweist der Link auf die Unterseite, auf der die große Version des Fotos eingebunden ist. Die Links müssen also erst noch manipuliert werden, bevor die Lightbox funktioniert:

JavaScript
$(document).ready( function() {
   var link = $('dd.imagecell a');
   link.each( function(i) {
      i += 1;
      $(this).attr('href', 'pictures/picture-' + i + '.jpg');
   });
});

Mit diesem JavaScript-Code werden die Verweise auf den Thumbnails auf die großen Versionen der jeweiligen Fotos umgelenkt. Alles in allem muss man also folgendes in den <head>-Bereich der index.html schreiben:

HTML/JavaScript
<link rel="stylesheet" type="text/css" href="css/jquery.lightbox-0.5.css" media="screen" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.lightbox-0.5.pack.js"></script>
<script type="text/javascript">
   $(document).ready( function() {
      var link = $('dd.imagecell a');
      link.each( function(i) {
         i += 1;
         $(this).attr('href', 'pictures/picture-' + i + '.jpg');
      });
      link.lightBox();
   });
</script>

Schon habt ihr mit etwas jQuery eine moderne Lightbox in eure Aperture-Galerie eingebaut.

Noch ein Hinweis am Rande: Die Pfade zum Warte-Bild, den Pfeilen und dem Close-Button sind in der lightbox.js festgelegt. Wenn ihr den images-Ordner also nicht im Galerie-Ordner ablegt, sondern bspw. darunter, müsst ihr diese Pfad-Angaben ändern, sonst wird die Lightbox nicht richtig angezeigt.

Ansonsten gilt wie immer: Wenn es Fragen gibt, nutzt die Kommentar-Funktion!

Weiterführende Links: