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
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>
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;
}
This will show the video, but it will not have any controls so we cannot interact with it yet.
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>
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;
}
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;
}
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;
}
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;
}
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
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;
}
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;
}
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;
}
This just removes the default appearance of the track.
Our player is visually ready and should look like this:
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>`;
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;
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;
}
})
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;
}
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;
}
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;
});
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;
});
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();
}
}
});
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)