Grundlagen der Gestaltung von Videoplayern
Im vorherigen Artikel über plattformübergreifende Videoplayer beschrieben wir, wie man einen plattformübergreifenden HTML-Videoplayer mit den Media- und Fullscreen-APIs erstellt. Dieser Folgeartikel behandelt, wie man diesen benutzerdefinierten Player gestaltet und ihn responsiv macht.
Das Beispiel in Aktion
Sie finden den Code für das aktualisierte, gestaltete Beispiel auf GitHub und können es live ansehen.
Vorläufige Änderungen vom ursprünglichen Beispiel
In diesem Abschnitt werden die Änderungen zusammengefasst, die am ursprünglichen Videoplayer-Beispiel vorgenommen wurden, um die Gestaltungsaufgabe zu erleichtern, bevor die eigentliche Arbeit begann.
HTML-Markup
Es wurden einige Änderungen am HTML-Markup vorgenommen, die im vorherigen Artikel gezeigt wurden. Die benutzerdefinierten Videosteuerungen und das <progress>
-Element sind nun in <div>
-Elementen enthalten, anstatt in ungeordneten Listenelementen.
Das Markup für die benutzerdefinierten Steuerungen sieht nun wie folgt aus:
<div id="video-controls" class="controls" data-state="hidden">
<button id="play-pause" type="button" data-state="play">Play/Pause</button>
<button id="stop" type="button" data-state="stop">Stop</button>
<div class="progress">
<progress id="progress" value="0" min="0">
<span id="progress-bar"></span>
</progress>
</div>
<button id="mute" type="button" data-state="mute">Mute/Unmute</button>
<button id="vol-inc" type="button" data-state="vol-up">Vol+</button>
<button id="vol-dec" type="button" data-state="vol-down">Vol-</button>
<button id="fs" type="button" data-state="go-fullscreen">Fullscreen</button>
</div>
Verwandte CSS-Änderungen
Der vorherige Artikel setzte die display
-Eigenschaft der Videosteuerungen auf block
, um sie anzuzeigen. Dies wurde nun geändert, um ein data-state
-Attribut zu verwenden, das dieser Code bereits zur Handhabung der Vollbild-Implementierung nutzt.
Diese "data-state"-Idee wird auch verwendet, um den aktuellen Zustand der Tasten innerhalb der Videosteuerungen festzulegen, was eine spezifische Zustandsgestaltung ermöglicht.
JavaScript
Wie oben erwähnt, wird ein data-state
-Attribut an verschiedenen Stellen für Gestaltungszwecke verwendet und diese werden mit JavaScript gesetzt. Spezifische Implementierungen werden an geeigneten Stellen unten erwähnt.
Gestaltung
Der hier verwendete Stil des resultierenden Videoplayers ist ziemlich einfach — dies ist beabsichtigt, da der Zweck darin besteht zu zeigen, wie ein solcher Videoplayer gestaltet und responsiv gemacht werden kann.
Hinweis: In einigen Fällen werden einige grundlegende CSS-Elemente aus den Codebeispielen hier ausgelassen, da ihre Verwendung entweder offensichtlich oder nicht speziell relevant für die Gestaltung des Videoplayers ist.
Grundlegende Gestaltung
Das HTML-Video und seine Steuerungen sind alle in einem <figure>
-Element enthalten, das eine maximale Breite und Höhe erhält (basierend auf den Abmessungen des verwendeten Videos) und zentriert auf der Seite platziert wird:
figure {
max-width: 64rem;
width: 100%;
max-height: 30.875rem;
height: 100%;
margin: 1.25rem auto;
padding: 1.051%;
background-color: #666666;
}
Der Container für die Videosteuerungen benötigt ebenfalls eine Gestaltung, damit er korrekt eingerichtet ist:
.controls {
width: 100%;
height: 8.0971659919028340080971659919028%; /* of figure's height */
position: relative;
}
Die Höhe der Klasse .controls
wird auf einen (sehr präzisen!) Prozentsatz des umschließenden <figure>
-Elements gesetzt (dies wurde experimentell basierend auf der erforderlichen Tastenhöhe erarbeitet). Seine Position ist ebenfalls speziell auf relative
gesetzt, was für seine Anpassungsfähigkeit erforderlich ist (darauf wird später näher eingegangen).
Wie bereits erwähnt wird nun ein data-state
-Attribut verwendet, um anzuzeigen, ob die Videosteuerungen sichtbar sind oder nicht und diese müssen ebenfalls gestaltet werden:
.controls[data-state="hidden"] {
display: none;
}
.controls[data-state="visible"] {
display: block;
}
Es gibt eine Reihe von Eigenschaften, die auch für alle Elemente innerhalb der Videosteuerungen festgelegt werden müssen:
.controls > * {
float: left;
width: 3.90625%;
height: 100%;
margin-left: 0.1953125%;
display: block;
}
.controls > *:first-child {
margin-left: 0;
}
Alle Elemente werden nach links gefloated, da sie nebeneinander ausgerichtet werden sollen, und jedes Element erhält eine width
von fast 4% (wiederum wurde der tatsächliche Wert basierend auf den erforderlichen Abmessungen der Tasten berechnet) und eine height
von 100%. Ein Wert für margin-left
wird ebenfalls festgelegt, aber das erste Element (in diesem Fall die Abspiel-/Pause-Taste) hat diese Eigenschaft durch den Wert 0 überschrieben.
Der <div>
-Container für das <progress>
-Element benötigt ebenfalls einige spezifische Einstellungen; er wird wesentlich breiter als die anderen Kind-Elemente und sein Cursor-Wert wird auf pointer gesetzt:
.controls .progress {
cursor: pointer;
width: 75.390625%;
}
Tasten
Die erste große Gestaltungsaufgabe besteht darin, die Tasten der Videosteuerungen wirklich wie echte Tasten aussehen und funktionieren zu lassen.
Jede Taste erhält eine grundlegende Gestaltung:
.controls button {
border: none;
cursor: pointer;
background: transparent;
background-size: contain;
background-repeat: no-repeat;
}
Standardmäßig haben alle <button>
-Elemente einen Rahmen, daher wird dieser entfernt. Da Hintergrundbilder verwendet werden, um die entsprechenden Symbole anzuzeigen, wird die Hintergrundfarbe der Taste auf transparent, nicht wiederholend gesetzt und das Element soll das Bild vollständig enthalten.
Die :hover
- und :focus
-Zustände werden dann für jede Taste eingestellt, die die Deckkraft der Taste verändert:
.controls button:hover,
.controls button:focus {
opacity: 0.5;
}
Um geeignete Tastenbilder zu erhalten, wurde ein Satz kostenloser gemeinsamer Steuerungsicons aus dem Web heruntergeladen. Jedes Bild wurde dann in eine Base64-codierte Zeichenfolge umgewandelt (mit einem Online-Base64-Bildencoder), da die Bilder ziemlich klein sind, sind die resultierenden codierten Zeichenfolgen ziemlich kurz.
Da einige Tasten eine Doppelfunktion haben, z.B. Abspielen/Pause und Stummschalten/Stummschaltung aufheben, haben diese Tasten verschiedene Zustände, die gestaltet werden müssen. Wie bereits erwähnt, wird eine data-state
-Variable verwendet, um anzuzeigen, in welchem Zustand sich solche Tasten derzeit befinden.
Zum Beispiel hat die Abspiel-/Pause-Taste die folgenden Hintergrundbilder-Definitionen (die vollständigen Base64-Zeichenfolgen wurden der Kürze halber weggelassen):
.controls button[data-state="play"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAA…");
}
.controls button[data-state="pause"] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAA…");
}
Wenn der data-state
der Taste geändert wird, wird auch das entsprechende Bild geändert. Alle anderen Tasten werden auf ähnliche Weise behandelt.
Fortschrittsbalken
Das <progress>
-Element verfügt über die folgende grundlegende Stilkonfiguration:
.controls progress {
display: block;
width: 100%;
height: 81%;
margin-top: 0.125rem;
border: none;
color: #0095dd;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
}
Wie die <button>
-Elemente hat auch das <progress>
-Element standardmäßig einen Rahmen, der hier entfernt wird. Es erhält auch eine leicht abgerundete Ecke aus ästhetischen Gründen.
Wie im vorherigen Artikel erwähnt, gibt es einen Fallback für Browser, die das <progress>
-Element nicht unterstützen; dieser muss ebenfalls angemessen gestaltet werden:
.controls progress[data-state="fake"] {
background: #e6e6e6;
height: 65%;
}
.controls progress span {
width: 0%;
height: 100%;
display: inline-block;
background-color: #2a84cd;
}
Eine .data-state
-Klasse wird hier auch verwendet, wenn ein <progress>
-Element "gefälscht" wird; wenn es sich in diesem Zustand befindet, muss die Hintergrundfarbe festgelegt werden. Das interne <span>
-Element, das als der tatsächliche Fortschrittsteil der gefälschten Fortschrittsleiste verwendet wird, hat seine Breite anfangs auf 0% gesetzt (es wird über JavaScript aktualisiert) und seine Hintergrundfarbe ebenfalls festgelegt.
Es gibt einige browser-spezifische Eigenschaften, die festgelegt werden müssen, um sicherzustellen, dass Firefox und Chrome die erforderliche Farbe für die Fortschrittsanzeige verwenden:
.controls progress::-moz-progress-bar {
background-color: #0095dd;
}
.controls progress::-webkit-progress-value {
background-color: #0095dd;
}
Obwohl dieselben Eigenschaften auf denselben Wert gesetzt sind, müssen diese Regeln separat definiert werden, sonst ignoriert Chrome sie.
JavaScript
Das war's wirklich für die unmittelbare Gestaltung; die nächste Aufgabe besteht darin, eine Reihe von JavaScript-Änderungen vorzunehmen, um sicherzustellen, dass alles wie erwartet funktioniert.
Steuerungen Sichtbarkeit
Die erste Änderung ist einfach: Der data-state
zum Anzeigen der Videosteuerungen, wenn JavaScript dem Browser zur Verfügung steht, muss nun gesetzt werden:
// Display the user defined video controls
videoControls.setAttribute("data-state", "visible");
Fortschrittsbalkenunterstützung
Es muss auch überprüft werden, um die "gefälschte" Fortschrittsanzeige einzurichten, wenn der Browser das <progress>
-Element nicht unterstützt:
const supportsProgress = document.createElement("progress").max !== undefined;
if (!supportsProgress) progress.setAttribute("data-state", "fake");
Tastenfunktionalität
Dieser Abschnitt behandelt das JavaScript, das zur Implementierung der Tastenfunktionalität erforderlich ist.
Abspielen/Pause und Stumm
Nachdem die Tasten nun wie Tasten aussehen und Bilder haben, die anzeigen, was sie tun, müssen einige Änderungen vorgenommen werden, damit die "Doppeleinstellungen"-Tasten (wie die Abspiel-/Pausetaste) im korrekten "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 Taste angibt:
function changeButtonState(type) {
if (type === "play-pause") {
// Play/Pause button
if (video.paused || video.ended) {
playPause.setAttribute("data-state", "play");
} else {
playPause.setAttribute("data-state", "pause");
}
} else if (type === "mute") {
// Mute button
mute.setAttribute("data-state", video.muted ? "unmute" : "mute");
}
}
Diese Funktion wird dann von den entsprechenden Event-Handlern aufgerufen:
video.addEventListener(
"play",
() => {
changeButtonState("play-pause");
},
false,
);
video.addEventListener(
"pause",
() => {
changeButtonState("play-pause");
},
false,
);
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 haben vielleicht bemerkt, dass es neue Handler gibt, bei denen auf die Video-Events play
und pause
reagiert wird. Dafür gibt es einen Grund! Auch wenn die Standard-Videosteuerungen des Browsers ausgeschaltet sind, machen viele Browser sie beim Rechtsklicken auf das HTML-Video zugänglich. Das bedeutet, dass ein Benutzer das Video über diese Steuerungen abspielen/pausieren könnte, was dann die benutzerdefinierten Steuerungs-Tasten aus dem Synch bringen würde. Wenn ein Benutzer die Standard-Steuerungen verwendet, werden die definierten Media API-Events — wie play
und pause
— ausgelöst, so dass dies genutzt werden kann, um die benutzerdefinierten Steuertasten synchron zu halten. Um dies zu gewährleisten, muss ein neuer Klick-Handler für die Abspiel-/Pausetaste definiert werden, so dass auch die Events play
und pause
ausgelöst werden:
playPause.addEventListener("click", (e) => {
if (video.paused || video.ended) {
video.play();
} else {
video.pause();
}
});
Lautstärke
Die Funktion alterVolume()
, die aufgerufen wird, wenn auf die Lautstärketasten des Players geklickt wird, ändert sich ebenfalls — sie ruft jetzt eine neue Funktion namens checkVolume()
auf:
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");
}
const alterVolume = (dir) => {
checkVolume(dir);
};
Diese neue Funktion checkVolume()
erledigt dasselbe wie alterVolume()
, sie legt aber auch den Zustand der Stummschalttaste abhängig von der aktuellen Lautstärkeeinstellung des Videos fest. checkVolume()
wird auch aufgerufen, wenn das volumechange
-Event ausgelöst wird:
video.addEventListener(
"volumechange",
() => {
checkVolume();
},
false,
);
Fortschrittsbalken
Eine kleine Änderung muss auch am Klick-Handler für das <progress>
-Element vorgenommen werden. Da das umschließende <figure>
-Element nun position:relative
darauf gesetzt hat, sind die Berechnungen, die dieser Klick-Handler macht, falsch. Es muss nun auch die Offset-Position des Elternelements berücksichtigen:
progress.addEventListener("click", (e) => {
const pos =
(e.pageX - progress.offsetLeft - progress.offsetParent.offsetLeft) /
progress.offsetWidth;
video.currentTime = pos * video.duration;
});
Vollbildmodus
Die Vollbild-Implementierung hat sich nicht geändert.
Responsive Gestaltung
Nachdem der Player nun seine grundlegende Optik und Haptik hat, müssen einige andere Gestaltungsänderungen — die Medienabfragen umfassen — vorgenommen werden, um ihn responsiv zu machen.
Der Player funktioniert derzeit ziemlich gut, bis er auf einem "mittleren" Bildschirm (z.B. 1024px/64em) oder kleiner angezeigt wird. In diesem Fall müssen die Ränder und Abstände des <figure>
-Elements entfernt werden, damit der gesamte verfügbare Platz genutzt wird, und die Tasten sind ein bisschen zu klein, daher muss dies geändert werden, indem eine neue Höhe auf dem Element festgelegt wird, das die .controls
-Klasse darauf hat:
@media screen and (width <= 64em) {
figure {
padding-left: 0;
padding-right: 0;
height: auto;
}
.controls {
height: 1.876rem;
}
}
Dies funktioniert gut genug, bis es auf einem kleineren Bildschirm (680px/42.5em) betrachtet wird, daher wird hier ein weiterer Haltepunkt gesetzt. Da sich die Höhe des .controls
-Klassen-Elements jetzt ändern wird, ist keine feste Höhe mehr erforderlich — sie wird daher auf auto
gesetzt. Die Definitionen für die Elemente innerhalb des .controls
-Elements müssen jetzt ebenfalls geändert werden:
@media screen and (width <= 42.5em) {
.controls {
height: auto;
}
.controls > * {
display: block;
width: 16.6667%;
margin-left: 0;
height: 2.5rem;
margin-top: 2.5rem;
}
.controls .progress {
position: absolute;
top: 0;
width: 100%;
float: none;
margin-top: 0;
}
.controls .progress progress {
width: 98%;
margin: 0 auto;
}
.controls button {
background-position: center center;
}
}
Der .progress
-Container wird jetzt durch position:absolute
an die Spitze des Steuerungssatzes verschoben, sodass er und alle Tasten breiter sein müssen. Zusätzlich müssen die Tasten unter den Fortschrittscontainer verschoben werden, damit sie sichtbar sind.