14.11. Einen Musikplayer mit jQuery UI erstellen

Problem

Sie benötigen einen Musikplayer, der eine Benutzeroberfläche mit den üblichen Steuerelementen bietet, egal ob die Musik über einen Flash Player, HTML 5 Audio oder eine andere Schnittstelle des Browsers abgespielt wird. Die Steuerelemente müssen barrierefrei, flexibel und mit einem Theme ausstaffierbar sein können. Dabei reichen aber ein paar grundlegende Features:

  • Abspielen
  • Pause
  • Ein Slider, um die aktuelle Abspielposition anzuzeigen und steuern zu können
  • Ein Fortschrittsbalken, um zu zeigen, wie weit das Lied schon gepuffert wurde
  • Lautstärkeregler

Neben diesen grundlegenden Features wollen Sie aber noch mehr Möglichkeiten anbieten können. Dieser Musikplayer muss skalierbar sein. Die gleiche Benutzerschnittstelle soll in jeder Größe funktionieren, egal ob der Player vom Browser, vom Anwender oder von der Anwendung in seiner Größe angepasst wurde – durchaus auch bis zur vollen Bildschirmgröße.

Lösung

Lassen Sie uns mit jQuery UI einen Musikplayer bauen. Wir werden die Buttons zum Abspielen und Pausieren mit Hilfe von Symbolen aus dem jQuery UI CSS Framework erstellen und für die Anzeige der aktuellen Position das jQuery UI-Plugin Slider nutzen. Der Fortschrittsbalken wird ein jQuery UI Progressbar sein. Für den Lautstärkeregler schließlich werden wir einen weiteren jQuery UI Slider nutzen. Diese Elemente stecken wir dann in einen normalen Container, so dass wir nicht nur die Widgets mit einem netten Theme versehen, sondern auch für den gesamten Player ein Theme vergeben können.


Note

Wir werden diesen Musikplayer nicht als wiederverwendbares Plugin erstellen, sondern einfach ein paar jQuery UI-Widgets so zusammenschrauben, dass sie für den Anwender wie eine Komponente erscheinen. Bei dem Musikplayer selbst wird es sich aber nicht um ein jQuery-Plugin oder jQuery UI-Plugin handeln. Für dieses Rezept ist er nur eine Zusammenstellung aus HTML-, JavaScript- und CSS-Code. So können wir uns darauf konzentrieren, wie wir die zugrundeliegenden jQuery UI-Plugins nutzen können, ohne uns um die zusätzliche Komplexität durch das Bauen eines neuen Plugins aus bestehenden Plugins kümmern zu müssen.


HTML5 Audio

Um das Ganze nicht zu kompliziert zu machen, werden wir eine minimale Untermenge der HTML5 Media Element API nutzen. Diese gibt es in einer Reihe aktueller Browser, wie zum Beispiel dem Firefox 3.5. Wir werden die API in einer Kompatibilitätsschicht implementieren, so dass auch andere Abspielmechanismen, wie der Flash Player, einfach eingesetzt werden können. Für dieses Rezept benötigen wir von unserer Audio-API die folgenden Funktionen:

  • Abspielen starten oder fortsetzen (play)
  • Abspielen pausieren (pause)
  • Länge des Liedes ermitteln (duration)
  • Aktuelle Position im Lied ermitteln (timeupdate)
  • An eine bestimmte Position im Lied springen (currentTime)
  • Die Lautstärke des Liedes bestimmen, mit der es gespielt wird (volumechange)
  • Die Lautstärke ermitteln (volume)

Wenn ein HTML5-audio-Element im Dokument vorhanden ist, ist dies der Code für die Kompatibilitätsschicht:

var $audio = $('audio'), audioEl = $audio[0];
var audio = {
        currentTime: 0,
        duration: secondsTotal,
        volume: 0.5,
        set: function(key, value) {
                this[key] = value;
                try { audioEl[key] = value; } catch(e) {}
                if (key == 'currentTime') {
                        $audio.trigger('timeupdate');
                }
                if (key == 'volume') {
                        $audio.trigger('volumechange');
                }
        },
        play: function() {
                audioEl.play && audioEl.play();
        },
        pause: function() {
                audioEl.pause && audioEl.pause();
        }
};
$audio.bind('timeupdate', function() {
        audio.currentTime = audioEl.currentTime;
});
audio.set('currentTime', 0);
audio.set('volume', 0.5);

Der Musikplayer

Lassen Sie uns die CSS-Klasse mplayer für unseren Musikplayer nutzen. Diese Klasse werden wir unserem Haupt-<div> zuweisen und sie wird als Präfix für all unsere CSS-Regeln und jQuery-Selektoren dienen. Hier der CSS- und HTML-Code für unser Player-Grundgerüst:

.mplayer { position: relative; width: 40%; height: 2.5em; margin: 50px 0 100px 0; }

<div class="mplayer ui-widget"></div>

Ich habe die Breite auf 40% gesetzt, so dass wir sehen können, dass wir einen flexiblen Player besitzen. Verändern Sie einfach die Größe des Browsers und beobachten Sie, wie sich der Player anpasst. Dies wird noch leichter zu erkennen sein, wenn der Player nicht mehr leer ist.

Neben der Klasse mplayer erhält unser Haupt-<div> eine Klasse ui-widget. Damit stellen wir sicher, dass die Elemente innerhalb des <div> einen passenden Style erhalten. Im nächsten Kapitel gehen wir detaillierter auf die Verwendung von Themes mit den Klassen des jQuery UI CSS Framework ein.

Ein leeres <div> und fehlendes JavaScript sorgen noch für einen ziemlich unsichtbaren und stillen Musikplayer. Lassen Sie uns einen Button zum Abspielen hinzufügen, um auch Musik zu hören.

Buttons zum Abspielen und Pausieren

Es gibt in jQuery UI noch kein Button-Plugin. Aber wir können ein a-Element und ein paar semantisch passende Symbol-Klassen aus dem jQuery UI CSS Framework nutzen:

Hier der CSS-Code:

.mplayer .buttons-container { position: absolute; top: 10px; left: 10px; }
.mplayer .buttons-container .playpause { height: 1.2em; width: 1.2em; display: block;
        position: relative; top: −2px; left: −2px; }
.mplayer .buttons-container .playpause .ui-icon { margin: −1px 0 0 −1px; }
.mplayer .playpause .ui-icon-play, .paused .playpause .ui-icon-pause { display: none; }
.paused .playpause .ui-icon-play { display: block; }

Und hier der HTML-Code:

<div class="mplayer ui-widget">
        <div class="buttons-container">
                <a class="playpause ui-state-default ui-corner-all" href="#">
                        <span class="ui-icon ui-icon-play"></span>
                        <span class="ui-icon ui-icon-pause"></span>
                </a>
        </div>
</div>

Mit ein paar CSS-Regeln bekommen wir einen Button, der sowohl zum Pausieren als auch zum Abspielen dient. Durch den eben angeführten CSS-Code wird immer nur ein Symbol – Abspielen oder Pause – gleichzeitig sichtbar sein, und zwar abhängig davon, ob unser div.mplayer die Klasse paused besitzt. Der HTML-Code ermöglicht es einem Designer aber auch, beide Symbole gleichzeitig anzuzeigen – vielleicht abhängig davon, ob ein Lied abgespielt wird, mit verschiedenen Farbe oder unterschiedlicher Opazität.

Hier der JavaScript-Code:

$('.mplayer .playpause').click(function() {
        var player = $(this).parents('.mplayer');
        if (player.is('.paused')) {
                $('.mplayer').removeClass('paused');
                audio.play();
        } else {
                $('.mplayer').addClass('paused');
                audio.pause();
        }
        return false;
})
.hover(function() { $(this).addClass('ui-state-hover'); },
        function() { $(this).removeClass('ui-state-hover'); })
.focus(function() { $(this).addClass('ui-state-focus'); })
.blur(function() { $(this).removeClass('ui-state-focus'); });
$('.mplayer').addClass('paused');

Unser Button benötigt JavaScript für folgende Aktionen:

  • Aufruf der Funktion audio.play() oder audio.pause() – je nachdem, ob die Klasse paused beim Klicken für den div.mplayer vorhanden ist.
  • Umschalten der Klasse paused für den .mplayer.
  • Reagieren auf die Maus- und Tastatur-Events focus, hover und blur. Hier wäre ein Button-Plugin praktisch (aktuell wird gerade eines gebaut), aber für einen einfachen Button mit einem Symbol brauchen wir zum Glück nicht zuviel Code.

Vergessen Sie nicht das return false;, da es sich bei unserm Button um ein <a> mit einer href # handelt.

Mit dem geladenen jQuery, jQuery UI und dem UI Lightness Theme zeigt Figure 14-1, wie unser Musik-Player nur mit dem Play/Pause-Button aussieht.

Play- und Pause-Button
Figure 14-1. Play- und Pause-Button

Wenn Sie auf den Play-Button klicken, sollte er zu einem Pause-Button werden. Klicken Sie ihn erneut an, dann sollte er zurückwechseln. Achten Sie auch auf den Hover-Effekt und einen visuellen Hinweis, wenn Sie per Tab-Taste zum Button (und auch wieder weg) springen. In einem Browser, der das audio-Element unterstützt, dessen Attribut src auch noch auf eine Musikdatei verweist, sollten Sie beim Klick auf Play sogar etwas hören.

Label für die aktuelle Position und die Länge des Liedes

Im nächsten Schritt fügen wir zwei Label hinzu, die die aktuelle Position im Lied und die gesamte Laufzeit des Liedes anzeigen. Beides ist einfach umzusetzen.

Hier der CSS-Code:

.mplayer .currenttime { position: absolute; top: 0.6em; left: 2.2em;
        width: 3em; text-align: center; background: none; border: none; }
.mplayer .duration { position: absolute; top: 0.6em; right: 2.2em;
        width: 3em; text-align: center; background: none; border: none; }

Hier der HTML-Code:

<div class="mplayer ui-widget">
        <div class="buttons-container">
                <a class="playpause ui-state-default ui-corner-all" href="#">
                        <span class="ui-icon ui-icon-play"></span>
                        <span class="ui-icon ui-icon-pause"></span>
                </a>
        </div>
        <span class="currenttime ui-state-default"></span>
        <span class="duration ui-state-default"></span>
</div>

Und hier der JavaScript-Code:

function minAndSec(sec) {
        sec = parseInt(sec);
        return Math.floor(sec / 60) + ":" + (sec % 60 < 10 ? '0' : '') + 
Math.floor(sec % 60);
}
$('.mplayer .currenttime').text(minAndSec(audio.currentTime));
$('.mplayer .duration').text(minAndSec(secondsTotal));

$audio
        .bind('timeupdate', function(event) {
                $('.mplayer .currenttime').text(minAndSec(audio.currentTime));
        });

Wir haben die aktuelle Position nach links und die Gesamtzeit nach rechts gesetzt. Damit haben wir im Raum dazwischen Platz für den Fortschrittsbalken (siehe Figure 14-2). Wir wollen, dass die aktuelle Position immer anzeigt, wo wir uns gerade im Lied befinden, daher binden wir sie an das Event timeupdate des Audio-Elements. Das Event selbst gibt uns die currentTime nicht. Dafür holen wir uns die Eigenschaft audio.currentTime, die wir mit einer kleinen Funktion formatieren, um aus den vom Audio-Player angegebenen Sekunden auf Minuten:Sekunden zu kommen.

Aktuelle Position und Gesamtzeit
Figure 14-2. Aktuelle Position und Gesamtzeit

Slider für die Position im Lied

Jetzt kommen wir langsam voran. Als nächstes ist nun der Slider für die aktuelle Position dran. Er besteht aus einem einfachen <div>, aber wir werden ihn mit einer Möglichkeit versehen, ihn an einem Handle »anzufassen« und diesen zu verschieben, indem wir für ihn .slider() aufrufen. Dabei nutzen wir die Slider-Option range: 'min', so dass der Bereich zwischen 0:00 und der aktuellen Position andersfarbig dargestellt wird. Und natürlich setzen wir max auf die Laufzeit des Liedes in Sekunden. Handelt es sich also um ein Lied, das 3,5 Minuten läuft, setzen wir max auf 210. Wir brauchen da gar nicht rechnen, da uns audio.duration schon die Anzahl der Sekunden im Lied gibt. Die anderen Standardwerte für Slider sind hier schon genau richtig: min: 0, step: 1.

Der CSS-Code:

.mplayer .track { top: 11px; margin: 0 5.2em; margin-top: −2px; 
        border-style: none; }
.mplayer .track .ui-slider-handle { border-left-width: 0; height: 1.1em; 
        top: −0.24em; width: 2px; margin-left: −3px; }

Der HTML-Code:

<div class="mplayer ui-widget">
        <div class="buttons-container">
               <a class="playpause ui-state-default ui-corner-all" href="#">
                       <span class="ui-icon ui-icon-play"></span>
                       <span class="ui-icon ui-icon-pause"></span>
               </a>
        </div>
        <span class="currenttime ui-state-default"></span>
        <div class="track"></div>
        <span class="duration ui-state-default"></span>
</div>

Der JavaScript-Code:

$('.mplayer .track')
        .slider({
                range: 'min',
                max: audio.duration,
                slide: function(event, ui) {
                        $('.ui-slider-handle', this).css('margin-left',
                                (ui.value < 3) ? (1 - ui.value) + 'px' : '');
                        if (ui.value >= 0 && ui.value <= audio.duration) {
                                audio.set('currentTime', ui.value);
                        }
                },
                change: function(event, ui) {
                        $('.ui-slider-handle', this).css('margin-left',
                                (ui.value < 3) ? (1 - ui.value) + 'px' : '');
               }
        })
        .find('.ui-slider-handle').css('margin-left', '0').end()
        .find('.ui-slider-range').addClass('ui-corner-left').end();

$audio
        .bind('timeupdate', function(event) {
                $('.mplayer .track').each(function() {
                        if ($(this).slider('value') != audio.currentTime) {
                                $(this).slider('value', audio.currentTime);
                        }
               });
               $('.mplayer .currenttime').text(minAndSec(audio.currentTime));
        });

Slider Handles sind mittig ausgerichtet – beim min-Wert befindet sich die linke Hälfte des Handle links vom Slider, beim max-Wert befindet sich die rechte Hälfte des Handle rechts vom Slider. Wir haben den Handle schon verschlankt und den linken Rahmen entfernt, so dass er ein bisschen besser in den Bereich passt. Aber wir müssen trotzdem noch ein paar Anpassungen vornehmen, wenn wir nahe an min sind. Dazu dienen diese Zeilen:

slide: function(event, ui) {
      $('.ui-slider-handle', this).css('margin-left',
               (ui.value < 3) ? (1 - ui.value) + 'px' : '');
      if (ui.value >= 0 && ui.value <= audio.duration) {
               audio.set('currentTime', ui.value);
      }
},
change: function(event, ui) {
        $('.ui-slider-handle', this).css('margin-left',
                (ui.value < 3) ? (1 - ui.value) + 'px' : '');
}

Im Callback slide prüfen wir zudem, ob der Wert gültig ist, bevor wir das Audio-Element anweisen, an diesen Punkt zu springen. Das ist dann der Fall, wenn der Anwender den Slider verschiebt und wir die Abspielposition im Lied verändern müssen. So können wir ein »Scrubbing« zulassen. Berücksichtigten wir das nur im Callback für change, dann würde sich die Audio-Position erst ändern, wenn der Anwender die Maus loslässt, nachdem er den Handle verschoben hat. Figure 14-3 zeigt den Slider, den wir eben erstellt haben.

Slider für die Position im Lied
Figure 14-3. Slider für die Position im Lied

Fortschrittsbalken, um den Pufferstatus anzuzeigen

Jetzt werden wir ein bisschen Spaß haben. Würden Sie mir glauben, dass wir zwei verschiedene jQuery UI-Plugins auf das gleiche Element loslassen können? Das funktioniert in diesem Fall sehr gut. Wir haben schon einen Slider, den wir als <div> erstellt und mit einem Aufruf von .slider() »aktiviert« haben. Neben dem Hinzufügen der Klasse ui-slider zu unserem .track-Element hat das Slider-Plugin von jQuery UI auch noch eine ganze Reihe weiterer Elemente erzeugt und an unseren Track gehängt – unter anderem ein Slider Handle (.ui-slider-handle) und einen Slider Range (.ui-slider-range), da wir range: 'min' definiert haben. Glücklicherweise ist das schon so gut wie alles. Das fragliche Element ist immer noch ein <div> und es ist immer noch unser <div>. Also wollen wir es doppelt nutzen und zudem .progressbar() aufrufen. Damit wird die Anzeige des Puffer-Status hinter der Bereichs-Anzeige dargestellt. Probieren Sie es einmal aus.

Dies ist der CSS-Code:

.mplayer .ui-progressbar .ui-progressbar-value { border-style: none; }

Dies ist der JavaScript-Code:

var secondsCached = 0, cacheInterval;
$('.mplayer .track')
        .progressbar({
                value: secondsCached / secondsTotal * 100
        })
        .find('.ui-progressbar-value').css('opacity', 0.2).end();

cacheInterval = setInterval(function() {
        secondsCached += 2;
        if (secondsCached > secondsTotal) clearInterval(cacheInterval);
        $('.mplayer .track.ui-progressbar')
                .progressbar('value', secondsCached / secondsTotal * 100);
}, 30);

Es gibt keinen HTML-Code, da wir das Element .track aus dem vorigen Abschnitt erneut verwenden. Und falls Sie es noch nicht bemerkt haben: Der Code, der sich um das Puffern kümmert, ist ein einziger Schwindel. Nicht, dass er nicht funktioniert, aber er zeigt nicht an, inwieweit ein Lied schon gepuffert ist, sondern er simuliert das nur. Aber das macht er toll! Hätten Sie wirklich eine Musik-Ressource, die geladen und gepuffert würde, und könnten Sie Ihre Audio-API darüber benachrichtigen, dann würden Sie das Event binden und den Fortschrittsbalken dementsprechend zwischen 0 und 100 setzen. Anders als beim Slider können Sie keinen eigenen max-Wert definieren. Aber das ist durchaus sinnvoll, denn der Fortschritt geht immer von 0 bis 100%, oder?

Okay, wir haben jetzt also einen Prototypen. Wenn die Seite geladen ist, wird der Puffer-Status zunehmen, so als ob die Datei schnell geladen wird, aber nicht so schnell wie bei einer lokalen Datei. Es macht Spaß, dabei zuzusehen. Figure 14-4 zeigt den von uns erzeugten Fortschrittsbalken. Aber was ist mit der »gefälschten« Anzeige? Da es sich nicht um einen echten Puffer-Status handelt, kann man auch darüber hinausspringen. Was passiert dann? Das hängt von Ihrer Audio-API und dem Backend ab. Haben Sie also keine Möglichkeit, den Puffer-Status zu ermitteln, oder ist es für Sie uninteressant, dann überspringen Sie das einfach. Oder aber lassen Sie es, damit es schicker aussieht.

Fortschrittsbalken, um den Puffer-Status anzuzeigen
Figure 14-4. Fortschrittsbalken, um den Puffer-Status anzuzeigen

Lautstärkeregler

Jetzt brauchen wir ein Steuerelement für die Lautstärke. Ein Slider ist da genau das richtige. Lassen Sie ihn von volume: 0 bis volume: 1 laufen und setzen Sie step auf 0.01:

$('.mplayer .volume').slider({
        max: 1,
        step: 0.01,
        value: audio.volume,
        slide: fnSlide,
        change: fnChange
});

Bumm! Warum nicht? Nun, natürlich würde es so funktionieren. Aber der Code würde einiges an Platz verbrauchen. Und die Ausrichtung kann auch ein Problem sein. Lassen wir den Slider horizontal anzeigen (was der Standard ist), dann rangeln wir mit der Anzeige für die Position um den Platz. Zudem würde unser Player »schief« aussehen. Sollten wir dann also dem Slider die Option orientation: 'vertical' hinzufügen? Nun, das würde zwar auch funktionieren, aber unser Player wäre dann 100 Pixel hoch – und das nur, um den Lautstärkeregler anzuzeigen. Die restlichen Steuerelemente brauchen nur um die 30 Pixel. Es muss einen besseren Weg geben.

Und den gibt es. Verbergen Sie den Slider für die Lautstärke, wenn Sie ihn nicht brauchen. Wir zeigen nur den Slider-Handle dauerhaft an und fügen ein kleines Lautsprecher-Symbol hinzu. Dann verbergen wir den Rest, indem wir die Höhe des Steuerelements auf 0 setzen. Bewegt der Anwender den Mauscursor über den Handle, setzen wir die Höhe auf 100 Pixel. Beim Verlassen entfernen wir das, so dass die Höhe wieder auf 0 zurückgesetzt wird. Dadurch, dass wir den Container absolut in einem relativen Wrapper positionieren, wird auch nicht die Gesamthöhe des Players beeinflusst, wenn alle Elemente sichtbar sind.

Es gibt nur ein Problem. Gehen wir einmal davon aus, dass die Lautstärke bei 0,1 ist, also 10%. Das würde bedeuten, dass der Handle sich nahe am unteren Rand befindet. Sollte der Handle herunterspringen? Oder der Balken nach oben? Und was passiert, wenn der Anwender ihn verschiebt? Zum Beispiel von 10% auf 90%? Dann würde er zurückspringen, wenn der Balken wieder verborgen wird. Das ist dumm.

Daher machen wir es so: Wir halten den Handle die ganze Zeit am selben Platz. Der Anwender zieht ihn hoch, um die Lautstärke zu erhöhen, und herunter, um sie zu verringern. Der Balken wird sich einschließlich des durch range: "min" schattierten Bereichs unter dem Handle entsprechend bewegen.

Dies ist der CSS-Code:

.mplayer .volume-container { position: absolute; top: 12px; right: 12px; }
.mplayer .volume { height: 0; margin-top: 5px; }

Und dies der HTML-Code:

<div class="mplayer ui-widget">
        <div class="buttons-container">
                <a class="playpause ui-state-default ui-corner-all" href="#">
                        <span class="ui-icon ui-icon-play"></span>
                        <span class="ui-icon ui-icon-pause"></span>
                </a>
        </div>
        <span class="currenttime ui-state-default"></span>
        <div class="track"></div>
        <span class="duration ui-state-default"></span>
        <div class="volume-container">
                <div class="volume">
                        <a href="#" class="ui-state-default ui-corner-all 
                         ui-slider-handle">
                                <span class="ui-icon ui-icon-volume-on"></span>
                        </a>
                </div>
        </div>
</div>

Hier der JavaScript-Code:

$('.mplayer .volume')
        .slider({
                max: 1,
                orientation: 'vertical',
                range: 'min',
                step: 0.01,
                value: audio.volume,
                start: function(event, ui) {
                        $(this).addClass('ui-slider-sliding');
                        $(this).parents('.ui-slider').css({
                                'margin-top': (((1 - audio.volume) * −100) + 5) + 'px',
                                'height': '100px'
                        }).find('.ui-slider-range').show();
                },
                slide: function(event, ui) {
                        if (ui.value >= 0 && ui.value <= 1) {
                                audio.set('volume', ui.value);
                        }
                        $(this).css({
                                'margin-top': (((1 - audio.volume) * −100) + 5) + 'px',
                                'height': '100px'
                        }).find('.ui-slider-range').show();
               },
               stop: function(event, ui) {
                        $(this).removeClass('ui-slider-sliding');
                        var overHandle = $(event.originalEvent.target)
                                .closest('.ui-slider-handle').length > 0;
                        if (!overHandle) {
                                $(this).css({
                                        'margin-top': '',
                                        'height': ''
                                }).find('.ui-slider-range').hide();
                        }
               },
               change: function(event, ui) {
                       if (ui.value >= 0 && ui.value <= 1) {
                               if (ui.value != audio.volume) {
                                        audio.set('volume', ui.value);
                               }
                       }
               }
        })
        .mouseenter(function(event) {
                if ($('.ui-slider-handle.ui-state-active').length) {
                        return;
                }
                $(this).css({
                        'margin-top': (((1 - audio.volume) * −100) + 5) + 'px',
                        'height': '100px'
                }).find('.ui-slider-range').show();
        })
        .mouseleave(function() {
                $(this).not('.ui-slider-sliding').css({
                        'margin-top': '',
                        'height': ''
                }).find('.ui-slider-range').hide();
        })
        .find('.ui-slider-range').addClass('ui-corner-bottom').hide().end();

Während des Ziehens passen wir den negativen margin-top des Balken umgekehrt zum aktuellen Wert an, um den Handle an einer Stelle zu halten. Das geschieht durch diesen Code:

$(this).parents('.ui-slider').css({
        'margin-top': (((1 - audio.volume) * −100) + 5) + 'px',
        'height': '100px'
})

Figure 14-5 zeigt den Lautstärke-Slider in unserem Player.

Lautstärke-Slider
Figure 14-5. Lautstärke-Slider

Bei dieser Interaktion müssen Sie erkennen, dass Sie nicht den Balken in die umgekehrte Richtung wie die Maus ziehen. Aber gleichzeitig bewegen sich Ihr Mauscursor, die Größe des schattierten Bereichs und die Lautstärke in die richtige Richtung. Weniger/Unten für weniger, mehr/oben für mehr. Wenn Sie möchten, können Sie den Mauscursor auch über das Symbol bewegen, so dass der Balken erscheint, Ihren Mauscursor an die gewünschte Position auf dem Balken bewegen und klicken.

Hintergrund des Widgets und letzte Feinarbeiten

Lassen Sie uns jetzt noch ein paar Elemente mit Klassen aus dem jQuery UI CSS Framework hinzufügen, um den Player so anzupassen, dass er zu den Steuerelementen darin passt:

Hier der CSS-Code:

.mplayer .bg { position: absolute; width: 100%; height: 100%; top: 0;
        bottom: 0; left: 0; right: 0; border: none; }
.mplayer .rod { position: absolute; top: −2px; left: −0.4%; right: −0.4%;
        width: 100.8%; height: 3px; overflow: hidden; border: none; }
.mplayer .hl { position: absolute; top: 2px; left: 1%; right: 1%; width: 98%;
        height: 1px; overflow: hidden; border: none; }
.mplayer .hl2 { position: absolute; top: 2px; left: 2%; right: 2%; width: 96%;
        height: 3px; overflow: hidden; border: none; }

Hier der JavaScript-Code:

$('.mplayer').each(function() {
        $('.bg:first', this).css('opacity', 0.7);
        $('.bg:last', this).css('opacity', 0.3);
})
$('.mplayer .rod').css('opacity', 0.4);
$('.mplayer .hl').css('opacity', 0.25);
$('.mplayer .hl2').css('opacity', 0.15);

Und der HTML-Code:

<div class="mplayer ui-widget">
        <div class="bg ui-widget-header ui-corner-bottom"></div>
        <div class="bg ui-widget-content ui-corner-bottom"></div>
        <div class="rod ui-widget-header"></div>
        <div class="hl ui-widget-content"></div>
        <div class="hl2 ui-widget-content"></div>
        <div class="buttons-container">
                <a class="playpause ui-state-default ui-corner-all" href="#">
                        <span class="ui-icon ui-icon-play"></span>
                        <span class="ui-icon ui-icon-pause"></span>
                </a>
        </div>
        <span class="currenttime ui-state-default"></span>
        <div class="track"></div>
        <span class="duration ui-state-default"></span>
        <div class="volume-container">
                <div class="volume">
                        <a href="#" class="ui-state-default ui-corner-all
                         ui-slider-handle">
                                <span class="ui-icon ui-icon-volume-on"></span>
                        </a>
                </div>
        </div>
</div>

Wir nutzen hier Opazität und Layering, um aus den jQuery UI-Themes noch ein paar mehr Schattierungen herauszuholen. In Figure 14-6 sehen Sie das fertige Produkt:

Widget-Hintergrund und Feinarbeiten
Figure 14-6. Widget-Hintergrund und Feinarbeiten

In Figure 14-7 sind schließlich noch Beispiele für den jQuery UI Musikplayer mit vorgefertigten jQuery UI-Themes zu sehen.

jQuery UI Musikplayer mit verschiedenen ThemeRoller-Themes
Figure 14-7. jQuery UI Musikplayer mit verschiedenen ThemeRoller-Themes
JQuery Kochbuch
titlepage.xhtml
part0000.html
part0001.html
part0002_split_000.html
part0002_split_001.html
part0002_split_002.html
part0003_split_000.html
part0003_split_001.html
part0003_split_002.html
part0003_split_003.html
part0003_split_004.html
part0003_split_005.html
part0003_split_006.html
part0003_split_007.html
part0004_split_000.html
part0004_split_001.html
part0004_split_002.html
part0004_split_003.html
part0004_split_004.html
part0004_split_005.html
part0004_split_006.html
part0004_split_007.html
part0004_split_008.html
part0004_split_009.html
part0004_split_010.html
part0004_split_011.html
part0004_split_012.html
part0004_split_013.html
part0004_split_014.html
part0004_split_015.html
part0004_split_016.html
part0004_split_017.html
part0004_split_018.html
part0005_split_000.html
part0005_split_001.html
part0005_split_002.html
part0005_split_003.html
part0005_split_004.html
part0005_split_005.html
part0005_split_006.html
part0005_split_007.html
part0005_split_008.html
part0005_split_009.html
part0005_split_010.html
part0005_split_011.html
part0005_split_012.html
part0005_split_013.html
part0006_split_000.html
part0006_split_001.html
part0006_split_002.html
part0006_split_003.html
part0006_split_004.html
part0006_split_005.html
part0006_split_006.html
part0006_split_007.html
part0006_split_008.html
part0006_split_009.html
part0006_split_010.html
part0007_split_000.html
part0007_split_001.html
part0007_split_002.html
part0007_split_003.html
part0007_split_004.html
part0007_split_005.html
part0007_split_006.html
part0007_split_007.html
part0007_split_008.html
part0007_split_009.html
part0007_split_010.html
part0007_split_011.html
part0008_split_000.html
part0008_split_001.html
part0008_split_002.html
part0008_split_003.html
part0008_split_004.html
part0008_split_005.html
part0008_split_006.html
part0008_split_007.html
part0008_split_008.html
part0008_split_009.html
part0008_split_010.html
part0008_split_011.html
part0008_split_012.html
part0008_split_013.html
part0008_split_014.html
part0008_split_015.html
part0008_split_016.html
part0008_split_017.html
part0008_split_018.html
part0008_split_019.html
part0008_split_020.html
part0008_split_021.html
part0008_split_022.html
part0009_split_000.html
part0009_split_001.html
part0009_split_002.html
part0009_split_003.html
part0009_split_004.html
part0009_split_005.html
part0009_split_006.html
part0009_split_007.html
part0009_split_008.html
part0009_split_009.html
part0009_split_010.html
part0010_split_000.html
part0010_split_001.html
part0010_split_002.html
part0010_split_003.html
part0010_split_004.html
part0010_split_005.html
part0010_split_006.html
part0010_split_007.html
part0010_split_008.html
part0010_split_009.html
part0010_split_010.html
part0010_split_011.html
part0011_split_000.html
part0011_split_001.html
part0011_split_002.html
part0011_split_003.html
part0011_split_004.html
part0011_split_005.html
part0011_split_006.html
part0011_split_007.html
part0011_split_008.html
part0011_split_009.html
part0011_split_010.html
part0011_split_011.html
part0012_split_000.html
part0012_split_001.html
part0012_split_002.html
part0012_split_003.html
part0012_split_004.html
part0012_split_005.html
part0012_split_006.html
part0012_split_007.html
part0012_split_008.html
part0013_split_000.html
part0013_split_001.html
part0013_split_002.html
part0013_split_003.html
part0013_split_004.html
part0013_split_005.html
part0013_split_006.html
part0013_split_007.html
part0013_split_008.html
part0013_split_009.html
part0013_split_010.html
part0013_split_011.html
part0013_split_012.html
part0014_split_000.html
part0014_split_001.html
part0014_split_002.html
part0014_split_003.html
part0014_split_004.html
part0014_split_005.html
part0014_split_006.html
part0014_split_007.html
part0014_split_008.html
part0014_split_009.html
part0014_split_010.html
part0014_split_011.html
part0015_split_000.html
part0015_split_001.html
part0015_split_002.html
part0015_split_003.html
part0015_split_004.html
part0015_split_005.html
part0015_split_006.html
part0015_split_007.html
part0015_split_008.html
part0015_split_009.html
part0015_split_010.html
part0016_split_000.html
part0016_split_001.html
part0016_split_002.html
part0016_split_003.html
part0016_split_004.html
part0016_split_005.html
part0016_split_006.html
part0016_split_007.html
part0016_split_008.html
part0016_split_009.html
part0017_split_000.html
part0017_split_001.html
part0017_split_002.html
part0017_split_003.html
part0017_split_004.html
part0017_split_005.html
part0017_split_006.html
part0017_split_007.html
part0017_split_008.html
part0017_split_009.html
part0017_split_010.html
part0017_split_011.html
part0018_split_000.html
part0018_split_001.html
part0018_split_002.html
part0018_split_003.html
part0018_split_004.html
part0018_split_005.html
part0018_split_006.html
part0019_split_000.html
part0019_split_001.html
part0019_split_002.html
part0019_split_003.html
part0019_split_004.html
part0019_split_005.html
part0019_split_006.html
part0019_split_007.html
part0019_split_008.html
part0019_split_009.html
part0019_split_010.html
part0020_split_000.html
part0020_split_001.html
part0020_split_002.html
part0020_split_003.html
part0020_split_004.html
part0020_split_005.html
part0020_split_006.html
part0020_split_007.html
part0020_split_008.html
part0021_split_000.html
part0021_split_001.html
part0021_split_002.html
part0021_split_003.html
part0021_split_004.html
part0021_split_005.html
part0021_split_006.html
part0021_split_007.html
part0021_split_008.html
part0021_split_009.html
part0022.html
part0023.html
part0024.html
part0025.html