影片播放器樣式基礎

在上一篇《跨瀏覽器影片播放器》文章中,我們介紹瞭如何使用 Media 和 Fullscreen API 構建一個跨瀏覽器的 HTML 影片播放器。本文將進一步探討如何為這個自定義播放器新增樣式,包括如何使其響應式。

HTML 標記

相比上一篇文章中的 HTML 標記,這裡做了一些修改。自定義影片控制元件和 <progress> 元素現在被包含在 <div> 元素中,而不是直接放在無序列表項內。

自定義控制元件的標記現在看起來如下:

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>

為了樣式設定的需要,在多個地方使用了 data-state 屬性,這些屬性是透過 JavaScript 設定的。具體的實現將在下面的適當位置介紹。

樣式

此處使用的影片播放器樣式相當基礎,這是故意的,目的是展示如何為影片播放器新增樣式並使其響應式。

注意: 在某些情況下,一些基礎 CSS 被省略了,因為它們的使用要麼很明顯,要麼與影片播放器的樣式設定關係不大。

基礎樣式

影片控制元件容器本身需要一些樣式設定,以確保其佈局正確。

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

position 設定為 relative,這是使其具有響應性的前提(稍後會詳細介紹)。

如前所述,data-state 屬性用於指示影片控制元件是否可見,因此需要相應的 CSS 宣告。

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

按鈕

要解決的第一個主要樣式問題是讓影片控制元件的按鈕看起來和用起來都像真正的按鈕。

每個按鈕都具有一些基礎樣式。

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;
}

每個按鈕的寬度和高度都設定為 2rem。預設情況下,所有 <button> 元素都有邊框,這裡將其移除。由於將使用背景影像來顯示相應的圖示,因此按鈕的背景顏色設定為透明,不重複,並且元素應完全包含影像。此外,還有一些不應在螢幕上可見的標籤文字,因此文字顏色設定為透明。

然後為每個按鈕設定 :hover:focus 狀態,這會改變按鈕的不透明度。

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

為了獲得合適的按鈕影像,從網上下載了一套免費的通用控制元件圖示集。然後,每個影像都被轉換為 base64 編碼的字串(使用線上 base64 圖片編碼器),因為影像很小,所以生成的編碼字串也很短。

由於某些按鈕具有雙重功能(例如,播放/暫停,靜音/取消靜音),這些按鈕有不同的狀態需要進行樣式設定。如前所述,data-state 變數用於指示這些按鈕當前所處的狀態。

例如,播放/暫停按鈕具有以下背景影像定義(為簡潔起見,已省略完整的 base64 字串)。

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…");
}

當按鈕的 data-state 更改時,相應的影像也會更改。所有其他按鈕也以類似的方式處理。

進度條

<progress> 元素的 <div> 容器啟用了 flex-grow,使其能夠擴充套件以填滿控制元件中剩餘的空間。它還顯示一個指標游標,表示它是可互動的。

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

<progress> 元素具有以下基本樣式設定:

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

<button> 元素一樣,<progress> 元素也有預設邊框,這裡將其移除。出於美觀考慮,還為其設定了輕微的圓角。

需要設定一些特定於瀏覽器的屬性,以確保 Firefox 和 Chrome 使用所需的進度條顏色。

css
.controls progress::-moz-progress-bar {
  background-color: #0095dd;
}

.controls progress::-webkit-progress-value {
  background-color: #0095dd;
}

儘管這些屬性被設定為相同的值,但這些規則需要單獨定義,否則如果其中一個選擇器不被識別,整個宣告可能會變得無效。

全屏

現在我們來設定全屏模式下的控制元件樣式。由於 <figure> 元素被置於全屏,我們可以使用 :fullscreen 偽類來定位它。我們做了幾件事:

  • 使用 height: 100% 使 figure 佔據整個螢幕。
  • 使用 flexbox 使控制元件欄固定在底部,同時影片保持居中。
  • 使容器透明,以顯示原生的背景色。
  • 隱藏 figcaption
  • 恢復控制元件行的背景顏色,以確保我們的黑色按鈕在背景為黑色時仍然可見。
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;
}

響應式樣式

現在播放器已經具備了基本的外觀和感覺,還需要進行一些其他的樣式更改——涉及媒體查詢——以使其具有響應性。

我們希望在小螢幕(680px/42.5em)上觀看時自定義控制元件佈局,因此在此定義了一個斷點。我們調整按鈕和進度條的尺寸和位置屬性,使它們的排列方式不同。

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;
  }
}

`.progress` 容器現在透過 position:absolute 移動到控制元件集的頂部,因此它和所有按鈕都需要更寬。此外,按鈕需要被推到進度條容器下方,以便它們可見。

JavaScript

這就是即時的樣式設定;接下來的任務是進行一系列 JavaScript 更改,以確保一切正常工作,主要重構按鈕邏輯。

播放/暫停和靜音

現在按鈕看起來像按鈕,並且具有指示其功能的影像,需要進行一些更改,以便“雙功能”按鈕(如播放/暫停按鈕)處於正確的“狀態”並顯示正確的影像。為了實現這一點,定義了一個名為 changeButtonState() 的新函式,它接受一個 type 變數,指示按鈕的功能。

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");
  }
}

然後,相關的事件處理程式會呼叫這個函式。

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");
});

您可能已經注意到,在響應影片的 playpause 事件的地方新增了處理程式。這是有原因的!儘管瀏覽器預設的影片控制元件集已被停用,但許多瀏覽器透過右鍵單擊 HTML 影片都可以訪問它們。這意味著使用者可以從這些控制元件播放/暫停影片,這會導致自定義控制元件集中的按鈕不同步。如果使用者使用了預設控制元件,就會觸發定義的 Media API 事件(如 playpause),因此可以利用這一點來確保自定義控制元件按鈕保持同步。我們的點選也會在呼叫 play()pause() 方法時觸發 playpause 事件,所以這裡不需要更改。

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

音量

當播放器的音量按鈕被點選時呼叫的 alterVolume() 函式也發生了變化——它現在呼叫一個名為 checkVolume() 的新函式。

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("-");
});

這個新的 checkVolume() 函式與 alterVolume() 作用相同,但它還會根據影片的當前音量設定來設定靜音按鈕的狀態。當觸發 volumechange 事件時,也會呼叫 checkVolume()

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

進度和全屏

進度條和全屏的實現沒有改變。

結果

警告: 示例影片聲音可能很大!