DEV Community

Cover image for How to Style a Video Player and Create a Custom Player
Shahed Nasser
Shahed Nasser

Posted on • Originally published at blog.shahednasser.com on

How to Style a Video Player and Create a Custom Player

This article was originally published on my personal blog.

In a previous tutorial, we looked into how to style an audio element with CSS. We looked at how to do it with the audio's pseudo-selectors, and how to create an audio player of our own for more flexibility in styling it.

In this tutorial, we'll learn how to style a video element with CSS. Similar to the previous tutorial, we'll see how to do it with pseudo selectors and how to create a video player of our own.

Using Pseudo-Element Selectors

Video elements, by default, is not visible. We need to add the controls attribute to the HTML tag to make it visible.

Default Video Element

By default, here's how a video element looks like:

Notice that the default video element looks different on every browser.

Video Pseudo-Element Selectors

Here are the video pseudo-element selectors that we can use to style a video element:

video::-webkit-media-controls-panel
video::-webkit-media-controls-play-button
video::-webkit-media-controls-volume-slider-container
video::-webkit-media-controls-volume-slider
video::-webkit-media-controls-mute-button
video::-webkit-media-controls-timeline
video::-webkit-media-controls-current-time-display
video::-webkit-full-page-media::-webkit-media-controls-panel
video::-webkit-media-controls-timeline-container
video::-webkit-media-controls-time-remaining-display
video::-webkit-media-controls-seek-back-button
video::-webkit-media-controls-seek-forward-button
video::-webkit-media-controls-fullscreen-button
video::-webkit-media-controls-rewind-button
video::-webkit-media-controls-return-to-realtime-button
video::-webkit-media-controls-toggle-closed-captions-button
Enter fullscreen mode Exit fullscreen mode

However, we'll see in the examples below that styling with these selectors mostly only works with Chrome.

So, it's recommended to view the examples below on Chrome to see how the styling works.

Style Video Player General Container

To style a video player's general container, which includes all of the elements in a video player, we can use the pseudo-element selector video::-webkit-media-controls-panel. In the example below, we use it to change the background color of the video player.

Style Play Button

To style a video player's play button, we can use the pseudo-element selector video::-webkit-media-controls-play-button. In the example below, we add a background color and a border-radius to the play button.

Style Volume Slider

To style a volume slider, we can use the pseudo-element selector video::-webkit-media-controls-volume-slider. In the example below, we add a background color to the volume slider as well as make some changes in its padding and margin.

Style Mute Button

To style the mute button, we can use the pseudo-element selector video::-webkit-media-controls-mute-button. In the example below, we add a background color as well as a border-radius to the mute button.

Style Timeline

To style the timeline of the video, we can use the pseudo-element selector video::-webkit-media-controls-timeline. In the example below, we add a background color as well as play with the padding and margin of the timeline.

Style Current Time

To style the current time of the video, we can use the pseudo-element selector video::-webkit-media-controls-current-time-display. In the example below, we change the text color of the current time.

Style Remaining Time

To style the remaining time of the video, we can use the pseudo-element selector video::-webkit-media-controls-time-remaining-display. In the example below, we change the text color of the remaining time.

Style the Full-Screen Button

To style the full-screen button of the video player, we can use the pseudo-element selector video::-webkit-media-controls-fullscreen-button. In the example below, we change the background color as well as the border radius of the button.

Create Custom Player

In this section, we'll cover how to create a custom video player. Creating a custom video player guarantees that the video looks the same on all browsers, rather than our styling being supported by some browsers and not others.

When creating a custom player, that means we also have to add the wiring in JavaScript to make sure all the video functionalities are added to the video.

We'll start first with the styling then move on to the JavaScript. You can find the full video player at the end of this section.

The video is from W3Schools and the icons are from Heroicons.

Style with CSS

We'll first add the video inside a div element, which will be the container for the video element and the controls:

<div class="video-player">
  <video id="video">
  <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4" />
</video>
</div>
Enter fullscreen mode Exit fullscreen mode

Then, we'll add a simple styling related to the sizing of the video element:

.video-player {
  width: 30rem;
  height: 16.5rem;
  position: relative;
}
video {
  width: 100%;
  height: 100%;
  background:black;
}
Enter fullscreen mode Exit fullscreen mode

This will show the video, but it will not have any controls so we cannot interact with it yet.

How to Style a Video Player and Create a Custom Player

Next, we'll add the controls. Add the following after the video element:

  <div class="controls">
    <button class="play-button control-button">
      <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
</svg>
    </button>
    <input type="range" min="0" max="100" class="timeline" value="0" />
    <button class="sound-button control-button">
      <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
</svg>
    </button>
    <button class="control-button fullscreen-button">
      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
</svg>
    </button>
  </div>
Enter fullscreen mode Exit fullscreen mode

This includes a play button, the timeline as a range element, a sound element to mute and unmute the video, and a full-screen button.

First, we'll add styling for the container of the controls. Add the following CSS:

.controls {
  display: flex;
  position: absolute;
  width: 100%;
  bottom: 0;
  background: linear-gradient(to bottom, transparent, #000);
  align-items: center;
  transition: opacity .2s;
}
Enter fullscreen mode Exit fullscreen mode

This will make the display flex to ensure the items are placed next to each other perfectly. It will also position the controls at the bottom of the video and add a gradient background that goes from transparent to black.

We'll also add some CSS to hide the controls when the video is playing and only show them on hover:

.video-player.playing .controls {
  opacity: 0;
}

.video-player:hover .controls {
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

Next, we'll style the buttons. We'll add general styling that will be common for all the buttons:

.control-button {
  border: none;
  background: transparent;
  cursor: pointer;
  opacity: .8;
  transition: all .2s;
}

.control-button:hover {
  opacity: 1;
}

.control-button svg {
  stroke: #fff;
  fill: transparent;
}
Enter fullscreen mode Exit fullscreen mode

This will remove the default background color and border of a button and add some nice opacity transition on hover. We're also setting the stroke and fill of the SVG icons inside the buttons.

Then, we'll add more specific styling for each of the buttons to specify the size of the icons. This is just because some of the buttons can be bigger than the others:

.control-button.play-button {
  height: 40px;
  width: 40px;
}

.control-button.sound-button {
  height: 40px;
  width: 40px;
}

.control-button.fullscreen-button {
  height: 35px;
  width: 35px;
}
Enter fullscreen mode Exit fullscreen mode

Finally, we need to style the timeline. The timeline is an input range element.

To style a range input element, we can use the following CSS pseudo-selectors:

.timeline::-webkit-slider-thumb
.timeline::-moz-range-thumb
.timeline::-ms-thumb
.timeline::-webkit-slider-runnable-track
.timeline::-moz-range-track
.timeline::-ms-track
Enter fullscreen mode Exit fullscreen mode

The first three are cross-browser pseudo-selectors for the thumb which is used to change the range value. The second three are cross-browser pseudo-selectors for the track of the range input.

We'll first add styling to the timeline range element as a whole:

.timeline {
  -webkit-appearance: none;
  width: calc(100% - 125px);
  height: .5em;
  background-color: rgba(255, 255, 255, .3);
  border-radius: 5px;
  background-size: 0% 100%;
  background-image: linear-gradient(#fff, #fff);
  background-repeat: no-repeat;
}
Enter fullscreen mode Exit fullscreen mode

This will set the width to 100% - 125px, where 125px is the width of the buttons combined with extra space. We also set the background color of the track.

Notice that we use the background-image attribute to indicate the time elapsed in the video. background-size will be used to indicate the progress of the video. In the beginning, it's 0%. Later on, we'll change background-size based on the video progress in JavaScript.

Next, we'll style the thumb used to change the current time of the video:

.timeline::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  cursor: pointer;
  opacity: 0;
  transition: all .1s;
  background-color: rgba(255, 255, 255, .8);
}

.timeline::-moz-range-thumb {
  -webkit-appearance: none;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  cursor: pointer;
  opacity: 0;
  transition: all .1s;
  background-color: rgba(255, 255, 255, .8);
}

.timeline::-ms-thumb {
  -webkit-appearance: none;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  cursor: pointer;
  opacity: 0;
  transition: all .1s;
  background-color: rgba(255, 255, 255, .8);
}

.timeline::-webkit-slider-thumb:hover {
  background-color: #fff;
}

.timeline:hover::-webkit-slider-thumb {
  opacity: 1;
}

.timeline::-moz-range-thumb:hover {
  background-color: #fff;
}

.timeline:hover::-moz-range-thumb {
  opacity: 1;
}

.timeline::-ms-thumb:hover {
  background-color: #fff;
}

.timeline:hover::-ms-thumb {
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

This sets its color to white with some opacity. Then, on hover, we set the opacity to 1. Note that the style properties are repeated for cross-platform pseudo-selectors. We are also setting the width, height, border-radius, and more.

Finally, we'll style the track of the timeline:

.timeline::-webkit-slider-runnable-track {
  -webkit-appearance: none;
  box-shadow: none;
  border: none;
  background: transparent;
}

.timeline::-moz-range-track {
  -webkit-appearance: none;
  box-shadow: none;
  border: none;
  background: transparent;
}

.timeline::-ms-track {
  -webkit-appearance: none;
  box-shadow: none;
  border: none;
  background: transparent;
}
Enter fullscreen mode Exit fullscreen mode

This just removes the default appearance of the track.

Our player is visually ready and should look like this:

How to Style a Video Player and Create a Custom Player

What's left is to wire the controls with JavaScript and add the video functionalities.

Add Functionalities With JavaScript

We'll start by declaring some variables we'll use in our code. We'll declare variables related to the icons of the buttons:

const play = `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
</svg>`;
const pause = `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>`;
const sound = `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
</svg>`;
const mute = `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" clip-rule="evenodd" />
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2" />
</svg>`;
Enter fullscreen mode Exit fullscreen mode

The reason we're declaring them in JavaScript is to change between pause and play icons based on whether the video is playing or not, and to change between sound and mute icons based on whether the video is muted or not.

Then, we'll declare variables for the HTML elements we created to be able to attach event listeners and more:

const playButton = document.querySelector('.play-button');
const video = document.getElementById('video');
const timeline = document.querySelector('.timeline');
const soundButton = document.querySelector('.sound-button');
const fullscreenButton = document.querySelector('.fullscreen-button');
const videoContainer = document.querySelector('.video-player');
let isFullScreen = false;
Enter fullscreen mode Exit fullscreen mode

We've also added the isFullScreen variable which we'll use later to toggle full-screen states.

We'll start with the most basic functionality in a video player which is playing or pausing the video. We'll add an event listener to the click event of the playButton. Inside the listener, we'll check if the video is paused or not with the property paused on video and media elements:

playButton.addEventListener('click', function () {
  if (video.paused) {
    video.play();
    videoContainer.classList.add('playing');
    playButton.innerHTML = pause;
  } else {
    video.pause();
    videoContainer.classList.remove('playing');
    playButton.innerHTML = play;
  }
})
Enter fullscreen mode Exit fullscreen mode

If the video is paused, we play it, we add the class playing to the video container, and we change the icon to the pause icon. The reason we add the class playing is that in the CSS earlier we added styling to hide the controls when the video is playing.

If you try it out now, you'll see that the video player now allows you to play and pause the video.

We'll also add a listener to the onended event, which is triggered when the video ends, to change the icon back to play:

video.onended = function () {
  playButton.innerHTML = play;
}
Enter fullscreen mode Exit fullscreen mode

Next, we'll add the functionality for the timeline. We'll first add a listener to the media element event ontimeupdate which is triggered as the video is playing to indicate the current time of the video is changing. We'll use it to change the background size of the timeline track as we mentioned above in the CSS section:

video.ontimeupdate = function () {
  const percentagePosition = (100*video.currentTime) / video.duration;
  timeline.style.backgroundSize = `${percentagePosition}% 100%`;
  timeline.value = percentagePosition;
}
Enter fullscreen mode Exit fullscreen mode

We use video.currentTime and video.duration to calculate the progress in percentage then change the value of the timeline range element and its background-size CSS property based on that percentage.

We'll also add a listener to the change event on the timeline range element. When the user drags the thumb the video's current time should change based on the position the user chose:

timeline.addEventListener('change', function () {
  const time = (timeline.value * video.duration) / 100;
  video.currentTime = time;
});
Enter fullscreen mode Exit fullscreen mode

If you test it now, you'll see that as the video progresses you'll be able to see the progress in the timeline element. You can also seek the video using the timeline.

Next, we'll add functionality to the sound button. When clicking on it, in the listener we will mute the video if it has sound and unmute it if the opposite. We'll also change the icon of the sound button based on whether the video is muted or not:

soundButton.addEventListener('click', function () {
  video.muted = !video.muted;
  soundButton.innerHTML = video.muted ? mute : sound;
});
Enter fullscreen mode Exit fullscreen mode

Notice that we use video.muted to determine if the video is currently muted and to change whether it is muted or not.

If you test it now, you should be able to mute and unmute the video using the sound button.

Finally, we'll add the functionality of the full-screen button. When the button is clicked, we'll check if the video is in full screen using the variable isFullScreen. If the video is not in full-screen, we make it full screen. If it's already in full-screen we exit full screen:

fullscreenButton.addEventListener('click', function () {
  if (!isFullScreen) {
      if (video.requestFullscreen) {
        video.requestFullscreen();
      } else if (video.webkitRequestFullscreen) { /* Safari */
        video.webkitRequestFullscreen();
      } else if (video.msRequestFullscreen) { /* IE11 */
        video.msRequestFullscreen();
      }
  } else {
      if (document.exitFullscreen) {
        document.exitFullscreen();
      } else if (document.webkitExitFullscreen) { /* Safari */
        document.webkitExitFullscreen();
      } else if (document.msExitFullscreen) { /* IE11 */
        document.msExitFullscreen();
      }
  }
});
Enter fullscreen mode Exit fullscreen mode

Note that when making the video full screen we use requestFullscreen, webkitRequestFullscreen or msRequestFullScreen based on what the current browser supports. Similarly, to exit full screen we use document.exitFullscreen, document.webkitExitFullscreen, or document.msExitFullscreen based on what the current browser supports.

If you test the full-screen button now, you should be able to switch to and from full screen for the video.

Final Video Player

Our video is now fully operational with play, pause, mute, unmute, full-screen, and seek functionalities. You can see the full video player below:

Conclusion

When styling video elements, you can use pseudo-selectors. However, the styling will not be supported by all browsers and the same player styling is not guaranteed.

Instead, you'll need to create your own custom video player like we did above. You can add as many functionalities as you want. Creating your own custom player is a far more flexible and better solution.

Top comments (0)