视频播放器样式基础

在之前的 跨浏览器视频播放器文章 中,我们描述了如何使用媒体和全屏 API 构建跨浏览器 HTML 视频播放器。本后续文章将介绍如何对这个自定义播放器进行样式设置,包括使其具有响应性。

示例实际操作

A video player with play, stop, volume and fullscreen controls, showing an image of a soldier.

你可以在 GitHub 上找到 更新的带样式示例 的代码,以及 在线查看

来自原始示例的初步修改

本节概述了对 原始视频播放器示例 进行的修改,以便在开始大量工作之前,简化样式设置任务。

HTML 标记

对之前文章中显示的 HTML 标记进行了一些更改。自定义视频控件和 <progress> 元素现在包含在 <div> 元素中,而不是位于无序列表项中。

自定义控件的标记现在如下所示

html
<div id="video-controls" class="controls" data-state="hidden">
  <button id="playpause" 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="volinc" type="button" data-state="volup">Vol+</button>
  <button id="voldec" type="button" data-state="voldown">Vol-</button>
  <button id="fs" type="button" data-state="go-fullscreen">Fullscreen</button>
</div>

之前文章将视频控件的 display 属性设置为 block 以便显示它们。现在已更改为使用 data-state 属性,该代码已经使用该属性来处理其 全屏实现

这个“data-state”概念也用于设置视频控件集中按钮的当前状态,这允许特定状态的样式设置。

JavaScript

如上所述,data-state 属性在各种地方用于样式设置目的,这些属性使用 JavaScript 设置。具体的实现将在下面的相应位置提到。

样式

这里使用的视频播放器样式相当基础 - 这是故意的,因为目的是展示如何对这样的视频播放器进行样式设置并使其具有响应性。

注意:在某些情况下,这里代码示例中省略了一些基本的 CSS,因为它的使用要么显而易见,要么与视频播放器的样式设置无关。

基本样式

HTML 视频及其控件都包含在 <figure> 元素中,该元素被赋予最大宽度和高度(基于所用视频的尺寸)并在页面中居中

css
figure {
  max-width: 64rem;
  width: 100%;
  max-height: 30.875rem;
  height: 100%;
  margin: 1.25rem auto;
  padding: 1.051%;
  background-color: #666;
}

视频控件容器本身也需要一些样式,以便以正确的方式设置它

css
.controls {
  width: 100%;
  height: 8.0971659919028340080971659919028%; /* of figure's height */
  position: relative;
}

.controls 类的 height 被设置为封闭的 <figure> 元素的(非常精确的!)百分比(这是根据所需的按钮高度进行实验后确定的)。它的位置也被专门设置为 relative,这是其响应性所必需的(稍后会详细介绍)。

如前所述,data-state 属性现在用于指示视频控件是可见还是不可见,这些属性也需要进行样式设置

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

.controls[data-state="visible"] {
  display: block;
}

还需要为视频控件中的所有元素设置一些属性

css
.controls > * {
  float: left;
  width: 3.90625%;
  height: 100%;
  margin-left: 0.1953125%;
  display: block;
}

.controls > *:first-child {
  margin-left: 0;
}

所有元素都浮动到左侧,因为它们要彼此并排排列,并且每个元素都被设置为具有接近 4% 的 width(实际值也是根据所需的按钮尺寸计算的),以及 100% 的 height。还设置了 margin-left 的值,但第一个元素(在本例中是播放/暂停按钮)被 0 的值覆盖了该属性。

<progress> 元素的 <div> 容器也需要一些特定的设置;它被设置为比其他子元素宽得多,并且其光标值被设置为指针

css
.controls .progress {
  cursor: pointer;
  width: 75.390625%;
}

按钮

第一个主要样式设置任务是使视频控件的按钮看起来像真正的按钮并像真正的按钮一样工作。

每个按钮都有一些基本的样式

css
.controls button {
  border: none;
  cursor: pointer;
  background: transparent;
  background-size: contain;
  background-repeat: no-repeat;
}

默认情况下,所有 <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> 元素具有以下基本样式设置

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

<button> 元素一样,<progress> 也有一个默认边框,这里将其删除。它还为了美观而被赋予了一个略微圆角。

之前文章 中所述,为不支持 <progress> 元素的浏览器提供了回退;这也需要进行适当的样式设置

css
.controls progress[data-state="fake"] {
  background: #e6e6e6;
  height: 65%;
}
.controls progress span {
  width: 0%;
  height: 100%;
  display: inline-block;
  background-color: #2a84cd;
}

当一个 <progress> 元素被“伪造”时,这里也会使用一个 .data-state 类;处于这种状态时,需要设置背景颜色。用作伪造进度条的实际进度部分的内部 <span> 元素的宽度最初被设置为 0%(它通过 JavaScript 更新),并且它的背景颜色也被设置。

需要设置一些浏览器特定的属性,以确保 Firefox 和 Chrome 使用所需的进度条颜色

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

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

尽管将相同的属性设置为相同的值,但这些规则需要分别定义,否则 Chrome 会忽略它。

JavaScript

这就是对直接样式设置的全部内容;下一个任务是对 JavaScript 进行一些更改,以确保一切按预期工作。

控制可见性

第一个更改很简单:当浏览器可以使用 JavaScript 时,需要设置用于显示视频控件的 data-state

js
// Display the user defined video controls
videoControls.setAttribute("data-state", "visible");

进度条支持

还需要检查一下,如果浏览器不支持 <progress> 元素,则设置“伪造”进度条

js
const supportsProgress = document.createElement("progress").max !== undefined;
if (!supportsProgress) progress.setAttribute("data-state", "fake");

按钮功能

本节介绍实现按钮功能所需的 JavaScript。

播放/暂停和静音

现在,按钮看起来像真正的按钮并且具有指示其功能的图像,需要进行一些更改,以确保“双重功能”按钮(例如播放/暂停按钮)处于正确的“状态”并显示正确的图像。为了便于此,定义了一个名为 changeButtonState() 的新函数,该函数接受一个类型变量,指示按钮的功能

js
function changeButtonState(type) {
  if (type === "playpause") {
    // 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("playpause");
  },
  false,
);

video.addEventListener(
  "pause",
  () => {
    changeButtonState("playpause");
  },
  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("playpause");
});

mute.addEventListener("click", (e) => {
  video.muted = !video.muted;
  changeButtonState("mute");
});

你可能已经注意到,在视频上对 playpause 事件做出反应的地方出现了新的处理程序。这是有原因的!即使浏览器默认的视频控制集已关闭,许多浏览器也会通过右键单击 HTML 视频来使其可访问。这意味着用户可以通过这些控件播放/暂停视频,这将使自定义控制集的按钮不同步。如果用户使用默认控件,则会触发定义的媒体 API 事件(如 playpause),因此可以利用它来确保自定义控制按钮保持同步。为了确保这一点,需要为播放/暂停按钮定义一个新的单击处理程序,以便它也能触发 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");
}

const alterVolume = (dir) => {
  checkVolume(dir);
};

这个新的 checkVolume() 函数执行与 alterVolume() 相同的操作,但它还会根据视频当前的音量设置来设置静音按钮的状态。checkVolume() 也在 volumechange 事件触发时被调用。

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

进度条

还需要对 <progress> 元素的单击处理程序进行一些小的更改。由于包含的 <figure> 元素现在在其上设置了 position:relative,因此该单击处理程序执行的计算不正确。它现在还需要考虑父元素的偏移位置。

js
progress.addEventListener("click", (e) => {
  const pos =
    (e.pageX - progress.offsetLeft - progress.offsetParent.offsetLeft) /
    progress.offsetWidth;
  video.currentTime = pos * video.duration;
});

全屏

全屏实现 并没有改变。

响应式样式

现在播放器已经解决了其基本外观和感觉,需要进行一些其他涉及媒体查询的样式更改,以使其具有响应性。

播放器目前运行得很好,直到在“中等”屏幕(例如 1024px/64em)或更小的屏幕上显示。在这种情况下,需要删除 <figure> 元素上的边距和填充,以便利用所有可用空间,并且按钮太小,需要通过在设置了 .controls 类的元素上设置新高度来更改。

css
@media screen and (max-width: 64em) {
  figure {
    padding-left: 0;
    padding-right: 0;
    height: auto;
  }

  .controls {
    height: 1.876rem;
  }
}

这在更小的屏幕(680px/42.5em)上观看时效果很好,因此这里又做了一个断点。由于 .controls 类元素的高度现在会变化,因此不再需要固定高度 - 因此将其设置为 auto.controls 元素中的元素定义也需要更改。

css
@media screen and (max-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;
  }
}

.progress 容器现在通过 position:absolute 移动到控制集的顶部,因此它和所有按钮都需要更宽。此外,按钮需要推到进度容器下方,以便它们可见。