Im vorherigen Artikel Cross Browser Video Player beschrieben wir, wie ein plattformübergreifender HTML-Videoplayer unter Nutzung der Media- und Fullscreen-APIs erstellt wird. In diesem Folgeartikel geht es darum, wie dieser benutzerdefinierte Player gestaltet werden kann, inklusive seiner Responsivität.
Es wurden einige Änderungen am HTML-Markup vorgenommen, das im vorherigen Artikel gezeigt wurde. Die benutzerdefinierten Videosteuerungen und das <progress>-Element befinden sich nun innerhalb von <div>-Elementen, anstatt in ungeordneten Listenelementen.
Das Markup für die benutzerdefinierten Kontrollen sieht nun folgendermaßen aus:
Ein data-state-Attribut wird an verschiedenen Stellen für Stilzwecke verwendet und diese werden mithilfe von JavaScript gesetzt. Spezifische Implementierungen werden an geeigneten Stellen unten erwähnt.
Der resultierende Videoplayer-Stil, der hier verwendet wird, ist ziemlich einfach — das ist beabsichtigt, da es darum geht zu zeigen, wie ein solcher Videoplayer gestaltet und responsiv gemacht werden kann.
Hinweis:
In einigen Fällen wird hier ein grundlegendes CSS aus den Codebeispielen weggelassen, da seine Verwendung entweder offensichtlich oder nicht speziell relevant für die Gestaltung des Videoplayers ist.
Die Position ist auf relative gesetzt, was für die Responsivität erforderlich ist (mehr dazu später).
Wie bereits erwähnt, wird ein data-state-Attribut verwendet, um anzuzeigen, ob die Videosteuerungen sichtbar sind oder nicht, und es benötigt entsprechende CSS-Deklarationen:
Die erste größere Stilgestaltungsaufgabe besteht darin, die Schaltflächen der Videokontrolle tatsächlich wie echte Schaltflächen aussehen und agieren zu lassen.
Jede Schaltfläche hat einige grundlegende Stilgestaltungen:
Jede Schaltfläche hat eine Breite und Höhe von 2rem. Standardmäßig haben alle <button>-Elemente einen Rahmen, weshalb dieser entfernt wird. Da Hintergrundbilder verwendet werden, um geeignete Symbole anzuzeigen, wird die Hintergrundfarbe der Schaltfläche auf transparent, nicht wiederholt und so eingestellt, dass das Element das Bild vollständig enthält. Außerdem gibt es einige Beschriftungstexte, die nicht auf dem Bildschirm sichtbar sein sollten, daher wird die Textfarbe auf transparent gesetzt.
Die :hover- und :focus-Zustände werden dann für jede Schaltfläche festgelegt, die die Deckkraft der Schaltfläche ändern:
Um geeignete Schaltflächenbilder zu erhalten, wurde ein Satz von kostenlosen, häufig verwendeten Steuerelement-Icons aus dem Internet heruntergeladen. Jedes Bild wurde dann in eine base64-codierte Zeichenfolge umgewandelt (mit einem Online-Base64 Image Encoder), da die Bilder ziemlich klein sind, sind die resultierenden codierten Zeichenfolgen ziemlich kurz.
Da einige Schaltflächen eine doppelte Funktionalität haben, z.B. Wiedergabe/Pause und Stummschalten/Entstummen, haben diese Schaltflächen unterschiedliche Zustände, die formatiert werden müssen. Wie bereits erwähnt, wird eine data-state-Variable verwendet, um anzuzeigen, in welchem Zustand sich solche Schaltflächen derzeit befinden.
Zum Beispiel hat die Wiedergabe/Pause-Schaltfläche die folgenden Hintergrundbilddefinitionen (die vollständigen base64-Zeichenfolgen wurden der Kürze halber weggelassen):
Der <div>-Container für das <progress>-Element hat sein flex-grow aktiviert, sodass es den restlichen Platz in den Steuerungselementen ausfüllt. Es zeigt auch einen Zeigerzeiger an, um anzuzeigen, dass es interaktiv ist.
Wie die <button>-Elemente hat auch <progress> einen Standardrand, der hier entfernt wird. Es erhält auch eine leicht abgerundete Ecke aus ästhetischen Gründen.
Es gibt einige browserspezifische Eigenschaften, die festgelegt werden müssen, um sicherzustellen, dass Firefox und Chrome die erforderliche Farbe für den Fortschrittsbalken verwenden:
Obwohl die gleichen Eigenschaften auf denselben Wert gesetzt sind, müssen diese Regeln separat definiert werden, oder die gesamte Deklaration könnte gültig werden, wenn ein Selektor nicht erkannt wird.
Jetzt formatieren wir die Kontrollen für den Vollbildmodus. Da das <figure>-Element in den Vollbildmodus gesetzt wird, können wir es mit der :fullscreen-Pseudoklasse ansprechen. Wir tun Folgendes:
Stellen Sie die figure auf die volle Bildschirmhöhe mit height: 100%
Lassen Sie die Steuerungsleiste am unteren Rand haften, während das Video zentriert bleibt, unter Verwendung von Flexbox
Machen Sie den Container transparent, um die native Hintergrundfarbe anzuzeigen
Verbergen Sie die figcaption
Stellen Sie die Hintergrundfarbe für die Steuerreihe wieder her, um sicherzustellen, dass unsere schwarzen Schaltflächen noch sichtbar sind, wenn der Hintergrund schwarz ist.
Nachdem der Player seine grundlegende Optik und Haptik erhalten hat, müssen einige andere Stiländerungen – unter Verwendung von Media Queries – vorgenommen werden, um ihn responsiv zu gestalten.
Wir möchten das Layout der Steuerungen anpassen, wenn es auf einem kleineren Bildschirm (680px/42.5em) betrachtet wird, daher wird hier ein Breakpoint definiert. Wir passen die Größen- und Positionseigenschaften für die Schaltflächen und den Fortschrittsbalken so an, dass sie anders angeordnet sind:
Der .progress-Container wird jetzt über position:absolute an die Spitze des Steuerungssatzes verschoben, sodass er und alle Schaltflächen breiter sein müssen. Außerdem müssen die Schaltflächen unter den Fortschrittscontainer geschoben werden, damit sie sichtbar sind.
Das war wirklich alles zur unmittelbaren Stilgestaltung; die nächste Aufgabe besteht darin, eine Reihe von JavaScript-Änderungen vorzunehmen, um sicherzustellen, dass alles wie erwartet funktioniert, vor allem, um die Logik der Schaltflächen neu zu gestalten.
const videoContainer = document.getElementById("videoContainer");
const video = document.getElementById("video");
const videoControls = document.getElementById("video-controls");
const playPause = document.getElementById("play-pause");
const stop = document.getElementById("stop");
const mute = document.getElementById("mute");
const volInc = document.getElementById("vol-inc");
const volDec = document.getElementById("vol-dec");
const progress = document.getElementById("progress");
const fullscreen = document.getElementById("fs");
// Hide the default controls
video.controls = false;
// Display the user defined video controls
videoControls.setAttribute("data-state", "visible");
Da die Schaltflächen jetzt tatsächlich wie Schaltflächen aussehen und Bilder anzeigen, die deren Funktionalität anzeigen, müssen einige Änderungen vorgenommen werden, damit die "doppelte Funktionalität" der Schaltflächen (wie die Wiedergabe/Pause-Schaltfläche) im richtigen "Zustand" sind und das richtige Bild anzeigen. Um dies zu erleichtern, wird eine neue Funktion namens changeButtonState() definiert, die eine Typvariable akzeptiert, die die Funktionalität der Schaltfläche angibt:
Diese Funktion wird dann von den relevanten Ereignishandlern aufgerufen:
js
video.addEventListener("play", () => {
changeButtonState("play-pause");
});
video.addEventListener("pause", () => {
changeButtonState("play-pause");
});
stop.addEventListener("click", (e) => {
video.pause();
video.currentTime = 0;
progress.value = 0;
// Update the play/pause button's 'data-state' which allows the
// correct button image to be set via CSS
changeButtonState("play-pause");
});
mute.addEventListener("click", (e) => {
video.muted = !video.muted;
changeButtonState("mute");
});
Sie werden bemerkt haben, dass es neue Handler gibt, wo auf die play- und pause-Ereignisse im Video reagiert wird. Dafür gibt es einen Grund! Auch wenn die standardmäßige Videosteuerung des Browsers abgeschaltet wurde, machen viele Browser sie durch einen Rechtsklick auf das HTML-Video zugänglich. Das bedeutet, dass ein Benutzer das Video über diese Steuerung abspielen/anhalten kann, was dazu führen würde, dass die Schaltflächen des benutzerdefinierten Steuerungssatzes nicht synchronisiert wären. Wenn ein Benutzer die Standardsteuerungen verwendet, werden die definierten Media-API-Ereignisse — wie play und pause — ausgelöst, sodass dies ausgenutzt werden kann, um sicherzustellen, dass die benutzerdefinierten Steuerungsschaltflächen synchronisiert bleiben. Unser Klick löst auch die play- und pause-Ereignisse aus, wenn die play()- und pause()-Methoden aufgerufen werden, sodass hier nichts geändert werden muss:
Die Funktion alterVolume(), die aufgerufen wird, wenn die Lautstärketasten des Players geklickt werden, ändert sich ebenfalls — sie ruft nun eine neue Funktion namens checkVolume() auf:
js
function checkVolume(dir) {
if (dir) {
const currentVolume = Math.floor(video.volume * 10) / 10;
if (dir === "+" && currentVolume < 1) {
video.volume += 0.1;
} else if (dir === "-" && currentVolume > 0) {
video.volume -= 0.1;
}
// If the volume has been turned off, also set it as muted
// Note: can only do this with the custom control set as when the 'volumechange' event is raised,
// there is no way to know if it was via a volume or a mute change
video.muted = currentVolume <= 0;
}
changeButtonState("mute");
}
function alterVolume(dir) {
checkVolume(dir);
}
volInc.addEventListener("click", (e) => {
alterVolume("+");
});
volDec.addEventListener("click", (e) => {
alterVolume("-");
});
Diese neue checkVolume()-Funktion tut das Gleiche wie die alterVolume(), aber sie setzt auch den Zustand der Stummschalttaste gemäß der aktuellen Lautstärkeeinstellung des Videos. checkVolume() wird auch aufgerufen, wenn das volumechange-Ereignis ausgelöst wird:
Die [Fortschrittsbalken]-(/de/docs/Web/Media/Guides/Audio_and_video_delivery/cross_browser_video_player#progress) und [Vollbild]-(/de/docs/Web/Media/Guides/Audio_and_video_delivery/cross_browser_video_player#fullscreen)-Implementierungen haben sich nicht geändert.
progress.addEventListener("click", (e) => {
if (!Number.isFinite(video.duration)) return;
const rect = progress.getBoundingClientRect();
const pos = (e.pageX - rect.left) / progress.offsetWidth;
video.currentTime = pos * video.duration;
});
video.addEventListener("loadedmetadata", () => {
progress.setAttribute("max", video.duration);
});
video.addEventListener("timeupdate", () => {
if (!progress.getAttribute("max"))
progress.setAttribute("max", video.duration);
progress.value = video.currentTime;
});
if (!document?.fullscreenEnabled) {
fullscreen.style.display = "none";
}
fullscreen.addEventListener("click", (e) => {
if (document.fullscreenElement !== null) {
// The document is in fullscreen mode
document.exitFullscreen();
// Set the fullscreen button's 'data-state' which allows the
// correct button image to be set via CSS
fullscreen.setAttribute("data-state", "go-fullscreen");
} else {
// The document is not in fullscreen mode
videoContainer.requestFullscreen();
fullscreen.setAttribute("data-state", "cancel-fullscreen");
}
});