Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Grundlagen zur Gestaltung eines Videoplayers

Im vorherigen Artikel über den plattformübergreifenden Videoplayer haben wir beschrieben, wie man einen plattformübergreifenden HTML-Videoplayer mit den Media- und Fullscreen-APIs erstellt. Dieser Folgeartikel befasst sich damit, wie dieser benutzerdefinierte Player gestaltet werden kann, einschließlich der Anpassung an verschiedene Bildschirmgrößen.

HTML-Markup

Es wurden einige Änderungen am HTML-Markup vorgenommen, das im vorherigen Artikel gezeigt wurde. Die benutzerdefinierten Videosteuerungen und das <progress>-Element befinden sich jetzt innerhalb von <div>-Elementen, anstatt in ungeordneten Listenelementen.

Das Markup für die benutzerdefinierten Steuerungen sieht nun folgendermaßen aus:

html
<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">
      <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>

Ein data-state-Attribut wird an verschiedenen Stellen zu Stylingzwecken verwendet und diese werden mit JavaScript gesetzt. Spezifische Implementierungen werden an geeigneten Stellen unten erwähnt.

Gestaltung

Der hier verwendete Videoplayer-Stil ist recht einfach — das ist beabsichtigt, da das Ziel ist, zu zeigen, wie ein solcher Videoplayer gestaltet und reaktionsfähig gemacht werden kann.

Hinweis: In einigen Fällen wird hier in den Codebeispielen auf einige grundlegende CSS verzichtet, da deren Nutzung entweder offensichtlich ist oder nicht spezifisch für die Gestaltung des Videoplayers relevant ist.

Grundlegende Gestaltung

Der Container für die Videosteuerungen selbst benötigt ein wenig Styling, damit er korrekt eingerichtet ist:

css
.controls {
  display: flex;
  align-items: center;
  overflow: hidden;
  width: 100%;
  height: 2rem;
  position: relative;
}

Die Position ist auf relative gesetzt, was für seine Reaktionsfähigkeit erforderlich ist (dazu später mehr).

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:

css
.controls[data-state="hidden"] {
  display: none;
}

Tasten

Die erste größere Stilaufgabe besteht darin, die Tasten der Videosteuerung so zu gestalten, dass sie tatsächlich wie echte Tasten aussehen und sich so verhalten.

Jede Taste hat einige grundlegende Stilmerkmale:

css
.controls button {
  width: 2rem;
  height: 2rem;
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  border: none;
  cursor: pointer;
  color: transparent;
  background-color: transparent;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

Jede Taste ist auf 2rem in Breite und Höhe gesetzt. Standardmäßig haben alle <button>-Elemente einen Rahmen, weshalb dieser entfernt wird. Da Hintergrundbilder verwendet werden, um die entsprechenden Symbole darzustellen, wird die Hintergrundfarbe der Taste auf transparent, nicht wiederholt und das Element soll das Bild vollständig enthalten. Darüber hinaus gibt es einen Text, der nicht auf dem Bildschirm sichtbar sein soll, daher wird die Textfarbe auf transparent gesetzt.

Die :hover- und :focus-Zustände werden dann für jede Taste festgelegt, die die Deckkraft der Taste verändern:

css
.controls button:hover,
.controls button:focus {
  opacity: 0.5;
}

Um passende Tastenbilder zu erhalten, wurde ein Satz kostenloser gängiger Kontrollsymbole aus dem Internet heruntergeladen. Jedes Bild wurde dann in einen base64-kodierten String umgewandelt (unter Verwendung eines Online-base64-Bildencoders), da die Bilder ziemlich klein sind, sind die resultierenden kodierten Strings recht kurz.

Da einige Tasten eine Doppelfunktion haben, z. B. Wiedergabe/Pause und Stumm/Unstumm, haben diese Tasten unterschiedliche Zustände, die gestaltet werden müssen. Wie bereits erwähnt, wird eine data-state-Variable verwendet, um anzugeben, in welchem Zustand sich solche Tasten momentan befinden.

Zum Beispiel hat die Wiedergabe/Pause-Taste folgende Hintergrundbilddefinitionen (die vollständigen base64-Strings wurden aus Gründen der Kürze weggelassen):

css
.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, ändert sich auch das entsprechende Bild. Alle anderen Tasten werden auf ähnliche Weise behandelt.

Fortschrittsbalken

Der <div>-Container für das <progress>-Element hat sein flex-grow aktiviert, sodass es den verbleibenden Platz in den Steuerungen ausfüllt. Außerdem zeigt es einen Zeiger-Cursor an, um anzuzeigen, dass es interaktiv ist.

css
.controls .progress {
  flex-grow: 1;
  cursor: pointer;
  height: 80%;
}

Das <progress>-Element hat den folgenden grundlegenden Stil:

css
.controls progress {
  display: block;
  width: 100%;
  height: 100%;
  border: none;
  color: #0095dd;
  border-radius: 2px;
  margin: 0 auto;
}

Wie die <button>-Elemente hat auch <progress> einen Standardrahmen, der hier entfernt wird. Es erhält zudem eine leicht abgerundete Ecke aus ästhetischen Gründen.

Es gibt einige browserspezifische Eigenschaften, die gesetzt werden müssen, um sicherzustellen, dass Firefox und Chrome die erforderliche Farbe für den Fortschrittsbalken verwenden:

css
.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, oder die gesamte Deklaration kann gültig werden, wenn ein Selektor nicht erkannt wird.

Vollbildmodus

Nun gestalten wir die Steuerungen für den Vollbildmodus. Da das <figure>-Element in den Vollbildmodus gesetzt wird, können wir es mit der :fullscreen-Pseudoklasse ansprechen. Wir machen Folgendes:

  • Das figure nimmt den gesamten Bildschirm ein mit height: 100%
  • Die Steuerungsleiste bleibt am unteren Rand, während das Video zentriert bleibt, unter Verwendung von Flexbox
  • Der Container wird transparent gemacht, um die native Hintergrundfarbe zu zeigen
  • Die figcaption wird ausgeblendet
  • Die Hintergrundfarbe der Steuerzeile wird wiederhergestellt, um sicherzustellen, dass unsere schwarzen Tasten sichtbar bleiben, wenn der Hintergrund schwarz ist.
css
figure:fullscreen {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  max-width: 100%;
  height: 100%;
  background-color: transparent;
}
figure:fullscreen video {
  margin-top: auto;
  margin-bottom: auto;
}
figure:fullscreen figcaption {
  display: none;
}
figure:fullscreen .controls {
  background-color: #666666;
}

Reaktionsfähiges Styling

Nun, da der Player sein grundlegendes Aussehen und Verhalten erhalten hat, müssen einige weitere Styling-Änderungen unter Einbeziehung von Media Queries vorgenommen werden, um ihn reaktionsfähig zu machen.

Wir möchten das Layout der Steuerungen anpassen, wenn es auf einem kleineren Bildschirm (680px/42,5em) angezeigt wird, sodass hier ein Breakpoint definiert wird. Wir passen die Größen- und Positionseigenschaften für die Tasten und die Fortschrittsleiste an, damit sie anders angeordnet sind:

css
@media screen and (width <= 42.5em) {
  .controls {
    height: auto;
  }

  .controls button {
    width: calc(100% / 6);
    margin-top: 2.5rem;
  }

  .controls .progress {
    position: absolute;
    top: 0;
    width: 100%;
    margin-top: 0;
    height: 2rem;
  }

  .controls .progress progress {
    width: 98%;
  }

  figcaption {
    text-align: center;
  }
}

Der .progress-Container wird nun per position:absolute an den oberen Rand des Steuerungssatzes verschoben, sodass er und alle Tasten breiter sein müssen. Außerdem müssen die Tasten unter den Fortschrittscontainer verschoben werden, damit sie sichtbar sind.

JavaScript

Das war es eigentlich schon mit dem unmittelbaren Styling; die nächste Aufgabe besteht darin, eine Reihe von JavaScript-Änderungen vorzunehmen, um sicherzustellen, dass alles wie erwartet funktioniert, hauptsächlich um die Logik der Tasten neu zu gestalten.

Wiedergabe/Pause und Stumm

Da die Tasten nun tatsächlich wie Tasten aussehen und Bilder haben, die anzeigen, was sie tun, müssen einige Änderungen vorgenommen werden, damit die "Doppelfunktion"-Tasten (wie die Wiedergabe/Pause-Taste) 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:

js
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 relevanten Ereignis-Handlern 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 haben vielleicht bemerkt, dass es neue Handler gibt, bei denen auf die play- und pause-Ereignisse des Videos reagiert wird. Es gibt einen Grund dafür! Auch wenn das standardmäßige Videosteuerungssatz im Browser deaktiviert wurde, machen viele Browser sie über das Rechtsklicken auf das HTML-Video zugänglich. Das bedeutet, dass ein Benutzer das Video über diese Steuerungen abspielen/pausieren könnte, wodurch die Tasten des benutzerdefinierten Steuerungssatzes nicht mehr synchron wären. Wenn ein Benutzer die Standardsteuerungen verwendet, werden die definierten Media API-Ereignisse — wie play und pause — ausgelöst, was genutzt werden kann, um sicherzustellen, dass die benutzerdefinierten Steuerungstasten synchron bleiben. Unser Klick löst auch die play- und pause-Ereignisse beim Aufrufen der play()- und pause()-Methoden aus, sodass hier nichts geändert werden muss:

js
playPause.addEventListener("click", (e) => {
  if (video.paused || video.ended) {
    video.play();
  } else {
    video.pause();
  }
});

Lautstärke

Die alterVolume()-Funktion, die aufgerufen wird, wenn die Lautstärketasten des Players angeklickt werden, ändert sich ebenfalls — sie ruft jetzt 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 alterVolume(), setzt aber auch den Zustand der Stummschalttaste in Abhängigkeit von der aktuellen Lautstärkeeinstellung des Videos. checkVolume() wird auch aufgerufen, wenn das volumechange-Ereignis ausgelöst wird:

js
video.addEventListener("volumechange", () => {
  checkVolume();
});

Fortschritt und Vollbild

Die [Fortschrittsbalken]- und [Vollbild]-Implementierungen haben sich nicht geändert.

Ergebnis

Warnung: Das Beispielvideo kann laut sein!