Videos are great tools for catching attention and even better for storytelling. And people, business and brands are using them more and more. However, native HTML5 video player offers only limited options for customization. When client wants a video player that matches her brand these options are not enough. Today, we will take a look at how to create our own custom video player. Let’s take a complete control so we can provide our clients with the solution they want! Table of Contents: Briefing HTML CSS (SASS) Styling the controls wrapper Styling the buttons and progress bar Putting It All Together JavaScript Move, mute and play, or pause Reset, stop, update and… listen Putting It All Together Codepen . demo Briefing In this tutorial our goal will be creating a HML5 video player with custom controls and progress bar. This video player will have seven control elements, or buttons. These buttons will add following functionalities: play or pause the video, move the video backward, move the video forward, stop the video, restart the video (move it to the beginning), mute or unmute the video and toggle fullscreen. We will implement these functionalities with JavaScript functions and event listeners. One of the buttons will be initially hidden, the button for restarting the video. We will show this button when the video reaches the end. Other buttons, such as play and mute will change its icon according to the current state of the video. In order to make these controls unobtrusive, we will hide them in the default state of the video player. And, we will show them only when user hovers over the video player or when the video player will get focus. This will help us with mobile devices. In order to make our work easier, we will not create the player icons from scratch. Instead, we will use icon font called . And, this will also be the only external resource we will need for this tutorial. Well, we will also need some video file, preferably in mp4 format and poster so we can show something while the video is loading or until the user hits the play button. Now, we can move to the HTML. Font Awesome Note: Since this is a tutorial on how to build a video player, let me give you some ideas for free video resources. In case of videos, I have three favorite go-to websites. These websites are Cover , Videezy and Pexels Videos . And, here is a short list of another 20 sources on published on DesignHooks. HTML The structure of our video player will be very simple. We will create one element with “video-wrapper”. Inside this will be element with tag nested inside it. element will have “video-element”. It will also have two attributes. First attribute will be and its value will be the location to the image we will use as a poster. Second attribute will be . This attribute will be set to “metadata”. div class div video source Video class poster preload When we set the attribute to “metadata”, when the page loads, the browser will load only metadata. It will not load the video itself. This could cause lower performance in case of a bigger video file. The tag inside the element will have two attributes as well. First attribute will be and its value be the location of our video file. Second attribute will be . The value of this attribute will be “video/mp4”. preload source video src type We could also use other formats such as Ogg and WebM. However, if we consider browser support, MP4 video format is probably the best option since it is supported by all browsers. The last thing we should do is include a short message for people whose browsers don’t support HTML5 video tag. We will put this message as a pure text inside the element, right under the tag. This message is basically only for IE 8 and Opera Mini. video source Next, after the element will be element with “video-controls”. And, yes, this will contain the buttons users will be able to use to control the video. As we discussed in briefing, we will create seven buttons. We will use elements. Inside every element will be one element. This will serve us as a placeholder for button icons provided by Font Awesome. video div class div button button span span The last element, right after our container with , will be another element with “progress-bar”. And, inside it will be one more element, now with “progress-bar-fill”. We will use this to show current progress or timeline of the video. We could use for the fill. However, we would then have to set its property to “block” via CSS. So, we can now use a block element right away and save a line of CSS. div buttons div class div class div span display Note: some people like to use “i” tag for icons. I think that “span” is a better choice. The purpose of “i” tag is defining a text that conveys an alternate voice or mood. Another use case for this tag can be a technical term, phrase from another language or a thought. I am not sure if icon label meets any of these conditions. If so, please correct me. For this reason, I like to use “span” tag since it is completely neutral and conveys no specific meaning. Code: <div class="video-wrapper"> <video class="video-element" poster="https://s3.amazonaws.com/coverr-public/poster/Porto_View.jpg" preload="metadata"> <source src="https://coverr.co/s3/mp4/Porto_View.mp4" type="video/mp4"> Your browser doesn't support the video tag. </video> <div class="video-controls"> <button class="btn btn-backward"><span class="fa fa-backward"></span></button> <button class="btn btn-play"><span class="fa fa-play"></span></button> <button class="btn btn-forward"><span class="fa fa-forward"></span></button> <button class="btn btn-stop"><span class="fa fa-stop"></span></button> <button class="btn btn-reset" hidden><span class="fa fa-undo"></span></button> <button class="btn btn-mute"><span class="fa fa-volume-off"></span></button> <button class="btn btn-expand"><span class="fa fa-expand"></span></button> </div> <div class="progress-bar"> <div class="progress-bar-fill"></div> </div> </div> CSS (SASS) The second phase of building our video player is about creating some custom styles. This will be relatively short and fast since we are using only a small amount of elements. Let’s begin with the “video-wrapper” . We will need to do three things. First, we will set its to “relative” so we can position the controls absolutely later. Next, we will set its to “hidden” so controls are visible only they should be. Finally, we will add some . div position overflow max-width Next on the line is the element. We will set its property to “block”. Next, we will set its to “100%” so it will fill the whole “video-wrapper” . And we will set its to “100%” as well so it will not overflow the “video-wrapper” . Finally, let’s also set its to “auto”, just to make sure the video will keep its aspect ratio. video display width div max-width div height Code: .video-wrapper { position: relative; overflow: hidden; max-width: 480px; } video { display: block; width: 100%; max-width: 100%; height: auto; } Styling the controls wrapper Next on the list is the “video-controls” and control elements, or , inside it. For the , we will set its to “absolute”, and to “-45px”. This is the final height of the whole . In other words, this will put the with control elements just below the visible area of the “video-wrapper”. Next, we will use combo of and properties to center the “video-controls” horizontally. div buttons div position bottom div div left transform div We can also add some to add a bit of space between the edge of this and the buttons inside it. After this, we should also add at least some light to make this wrapper more visible. I think that “rgba(255, 255, 255, .35)” will be enough. Finally, let’s add and apply it to its property so the wrapper smoothly slide up into the view when user hovers over the wrapper. padding div background-color transition bottom This sliding effect will be quite easy to achieve. We will use and states of “video-wrapper” to change the property of “video-controls” to “12px” and its to “2” (just to make sure “video-controls” is always on top). We could set the property to “0” since this would be enough to show it. However, I decided to add a few more pixels to offset the “video-controls” from the bottom of the . hover focus div bottom z-index div bottom div video Code: // Video controls .video-controls { position: absolute; bottom: -45px; // height of controls container left: 50%; padding: 7px; background: rgba(255, 255, 255, .35); transform: translateX(-50%); transition: bottom .25s ease-in; } .video-wrapper:focus > video + .video-controls, .video-wrapper:hover > video + .video-controls { bottom: 12px; z-index: 2; } Styling the buttons and progress bar And, we are in the third and last phase of building our video player. Now, we will add some styles to and progress bar. Let’s tackle first. We are going to create nice and subtle square buttons with transparent background, without any border. We will achieve the square shape by using a combo of , and properties. We can get rid of the by setting it to “0”. buttons buttons padding width text-align border Next, we can increase the size icons by setting the property to “18px”. After that, let’s set the the to “#fff” (or white) and the to “transparent”. Let’s also make sure that when user hovers over any button, the mouse cursor will change to . Next, let’s remove the outline on focus. Finally, when user hovers over the button, let’s change its to “.25”. And, we can make this smoother with . font-size color buttons background-color pointer opacity transition The last piece is the progress bar. For this element, we will need four lines of CSS. First, we will make sure it has no . Second, we will give it some default . Third, let’s give it some nice and visible , like “#e74c3c” (red). Fourth, let’s again use , but now let’s apply it to property. When the video is playing, we will use JavaScript to change the on the fly. And, this is all CSS we need for our video player! width height background-color transition width width Code: // Video controls .btn { padding: 5px 0; width: 31px; font-size: 18px; text-align: center; color: #fff; background-color: transparent; border: 0; cursor: pointer; transition: opacity .175s ease-in; &:focus { outline: 0; } &:hover { opacity: .25; } } // Progress bar .progress-bar-fill { width: 0; height: 4px; background-color: #e74c3c; transition: width .35s linear; } Putting It All Together Before we move on to JavaScript, let’s take all the styles we previously created and put them together. So, this is what we have. Code: .video-wrapper { position: relative; overflow: hidden; max-width: 480px; } video { display: block; width: 100%; max-width: 100%; height: auto; } // Video controls .video-controls { position: absolute; bottom: -45px; // height of controls container left: 50%; padding: 7px; background: rgba(255, 255, 255, .35); transform: translateX(-50%); transition: bottom .25s ease-in; } .video-wrapper:focus > video + .video-controls, .video-wrapper:hover > video + .video-controls { bottom: 12px; z-index: 2; } // Video controls .btn { padding: 5px 0; width: 31px; font-size: 18px; text-align: center; color: #fff; background-color: transparent; border: 0; cursor: pointer; transition: opacity .175s ease-in; &:focus { outline: 0; } &:hover { opacity: .25; } } // Progress bar .progress-bar-fill { width: 0; height: 4px; background-color: #e74c3c; transition: width .35s linear; } JavaScript This is the last part of this video player tutorial. And, it also the most important step in order to make our video player work. Let’s get right into it. First, we will store all elements we will use, such as buttons, icons, video element, progress bar and fill, inside . When we are done with this step, we can create our first . This will allow users play the video in a fullscreen mode. So, we can call this “expandVideo”. consts function function function This uses a method that is still more or less experimental and in order to make it work, we will need to use different variations for different browsers. For this reason, we will use an statement. This statement will test if the browsers supports either , or . If so, it will use that method to toggle fullscreen. function requestFullscreen() if if requestFullscreen() mozRequestFullScreen() webkitRequestFullscreen() Code: const btnBackward = document.querySelector('.btn-backward'); const btnExpand = document.querySelector('.btn-expand'); const btnMute = document.querySelector('.btn-mute'); const btnMuteIcon = btnMute.querySelector('.fa'); const btnPlay = document.querySelector('.btn-play'); const btnPlayIcon = btnPlay.querySelector('.fa'); const btnForward = document.querySelector('.btn-forward'); const btnReset = document.querySelector('.btn-reset'); const btnStop = document.querySelector('.btn-stop'); const progressBar = document.querySelector('.progress-bar'); const progressBarFill = document.querySelector('.progress-bar-fill'); const videoElement = document.querySelector('.video-element'); // Toggle full-screen mode const expandVideo = () => { if (videoElement.requestFullscreen) { videoElement.requestFullscreen(); } else if (videoElement.mozRequestFullScreen) { // Version for Firefox videoElement.mozRequestFullScreen(); } else if (videoElement.webkitRequestFullscreen) { // Version for Chrome and Safari videoElement.webkitRequestFullscreen(); } } Move, mute and play, or pause Now, let’s add functionality to our video player to allow users to move five seconds backward or forward, mute the sound and, most importantly, play or pause the video. We can call these “moveBackward”, “moveForward”, “muteVideo” and “playPauseVideo”. The “moveBackward”, “moveForward” *functions will be simple. We will get the current time of the video (videoElement) and either increase or decrease it for 5 seconds (or any number you want). functions Next, let’s can create the fourth that will allow the user to mute and unmute the sound of the video, the “muteVideo” . We will again use statement that will ask the whether it is currently muted or not. If the video is muted, we will set property of the video to “false” and change the icon of the “mute” . Otherwise, we will set to “true”, and again change the icon of the . function function if videoElement muted button muted button It is time to implement the fifth and most important functionality of our video player, the “playPauseVideo” . This will basically copy the structure of the “muteVideo” . Now, we will not test if the video is muted, or its property. Instead, we will test its property. If it is paused, we will use method to play the video and change icon from “Play” to “Pause”. Otherwise, we will use and change the icon from “Pause” to “Play”. function function function mute paused play() button pause() Code: // Move the video backward for 5 seconds const moveBackward = () => { videoElement.currentTime -= 5; } // Move the video forward for 5 seconds const moveForward = () => { videoElement.currentTime += 5; } // Mute the video const muteVideo = () => { if (videoElement.muted) { videoElement.muted = false; btnMuteIcon.classList.remove('fa-volume-up'); btnMuteIcon.classList.add('fa-volume-off'); } else { videoElement.muted = true; btnMuteIcon.classList.remove('fa-volume-off'); btnMuteIcon.classList.add('fa-volume-up'); } } // Play / Pause the video const playPauseVideo = () => { if (videoElement.paused) { videoElement.play(); btnPlayIcon.classList.remove('fa-play'); btnPlayIcon.classList.add('fa-pause'); } else { videoElement.pause(); btnPlayIcon.classList.remove('fa-pause'); btnPlayIcon.classList.add('fa-play'); } } Reset, stop, update and… listen This is the last part in this tutorial and the last three , “restartVideo”, “stopVideo” and “updateProgress”. “restartVideo” will set the property of the video to “0”. Then, it will show the “play” (we will hide it with event listener when the video reaches its end) and hide the “Restart” . “stopVideo” will use method to pause the video, set the property of the video to “0” and change the icon “Play” from “Pause” to “Play”. functions function currentTime button button function pause() currentTime button The very last our video player needs is “updateProgress”. This will dynamically update the CSS property of the progress bar according to the current time of the video. First, we will divide the whole of the video by 100 and multiplying it by . This will give us the correct value for progress bar in percentages so we can then apply it. function width duration currentTime width The final step is attaching some to video player controls. We will use method to attach an event listener to all , btnBackward, btnExpand, btnMute, btnPlay, btnForward, btnReset, btnStop. All these will listen to is “click” and use one of the we previously created. Lastly, we will add two more for the . event listeners addEventListener() buttons event listeners functions event listeners videoElement One be for “ended” event and one for “timeupdate”. for “ended” event will hide the “Play” and show the “Reset” . for “timeupdate” will apply our function. And, that’s it! event listener Listener button button Listener updateProgress() Code: // Restart the video const restartVideo = () => { videoElement.currentTime = 0; btnPlay.removeAttribute('hidden'); btnReset.setAttribute('hidden', 'true'); } // Stop the video const stopVideo = () => { videoElement.pause(); videoElement.currentTime = 0; btnPlayIcon.classList.remove('fa-pause'); btnPlayIcon.classList.add('fa-play'); } // Update progress bar as the video plays const updateProgress = () => { // Calculate current progress let value = (100 / videoElement.duration) * videoElement.currentTime; // Update the slider value progressBarFill.style.width = value + '%'; } // Event listeners btnBackward.addEventListener('click', moveBackward, false); btnExpand.addEventListener('click', expandVideo, false); btnMute.addEventListener('click', muteVideo, false); btnPlay.addEventListener('click', playPauseVideo, false); btnForward.addEventListener('click', moveForward, false); btnReset.addEventListener('click', restartVideo, false); btnStop.addEventListener('click', stopVideo, false); videoElement.addEventListener('ended', () => { btnPlay.setAttribute('hidden', 'true'); btnReset.removeAttribute('hidden'); }, false); videoElement.addEventListener('timeupdate', updateProgress, false); Putting It All Together Now, let’s take all the JavaScript we created for our video player and put them into one piece. Code: const btnBackward = document.querySelector('.btn-backward'); const btnExpand = document.querySelector('.btn-expand'); const btnMute = document.querySelector('.btn-mute'); const btnMuteIcon = btnMute.querySelector('.fa'); const btnPlay = document.querySelector('.btn-play'); const btnPlayIcon = btnPlay.querySelector('.fa'); const btnForward = document.querySelector('.btn-forward'); const btnReset = document.querySelector('.btn-reset'); const btnStop = document.querySelector('.btn-stop'); const progressBar = document.querySelector('.progress-bar'); const progressBarFill = document.querySelector('.progress-bar-fill'); const videoElement = document.querySelector('.video-element'); // Toggle full-screen mode const expandVideo = () => { if (videoElement.requestFullscreen) { videoElement.requestFullscreen(); } else if (videoElement.mozRequestFullScreen) { // Version for Firefox videoElement.mozRequestFullScreen(); } else if (videoElement.webkitRequestFullscreen) { // Version for Chrome and Safari videoElement.webkitRequestFullscreen(); } } // Move the video backward for 5 seconds const moveBackward = () => { videoElement.currentTime -= 5; } // Move the video forward for 5 seconds const moveForward = () => { videoElement.currentTime += 5; } // Mute the video const muteVideo = () => { if (videoElement.muted) { videoElement.muted = false; btnMuteIcon.classList.remove('fa-volume-up'); btnMuteIcon.classList.add('fa-volume-off'); } else { videoElement.muted = true; btnMuteIcon.classList.remove('fa-volume-off'); btnMuteIcon.classList.add('fa-volume-up'); } } // Play / Pause the video const playPauseVideo = () => { if (videoElement.paused) { videoElement.play(); btnPlayIcon.classList.remove('fa-play'); btnPlayIcon.classList.add('fa-pause'); } else { videoElement.pause(); btnPlayIcon.classList.remove('fa-pause'); btnPlayIcon.classList.add('fa-play'); } } // Restart the video const restartVideo = () => { videoElement.currentTime = 0; btnPlay.removeAttribute('hidden'); btnReset.setAttribute('hidden', 'true'); } // Stop the video const stopVideo = () => { videoElement.pause(); videoElement.currentTime = 0; btnPlayIcon.classList.remove('fa-pause'); btnPlayIcon.classList.add('fa-play'); } // Update progress bar as the video plays const updateProgress = () => { // Calculate current progress let value = (100 / videoElement.duration) * videoElement.currentTime; // Update the slider value progressBarFill.style.width = value + '%'; } // Event listeners btnBackward.addEventListener('click', moveBackward, false); btnExpand.addEventListener('click', expandVideo, false); btnMute.addEventListener('click', muteVideo, false); btnPlay.addEventListener('click', playPauseVideo, false); btnForward.addEventListener('click', moveForward, false); btnReset.addEventListener('click', restartVideo, false); btnStop.addEventListener('click', stopVideo, false); videoElement.addEventListener('ended', () => { btnPlay.setAttribute('hidden', 'true'); btnReset.removeAttribute('hidden'); }, false); videoElement.addEventListener('timeupdate', updateProgress, false); Closing thoughts on building a custom video player Congratulations! You’ve built your own HTML5 video player with custom controls and progress bar. I hope that this tutorial was easy to follow and that you enjoyed it. I also hope that you have a chance to either learned something new or at train your skills. From now, whenever your client, employer or anyone else will ask you to build a custom video player that matches his website and brand, you will know exactly how to do it. Good job! Thank you very much for your time. And, until next time, have a great day! . Did you like this article? Please subscribe Are you on social media? Let’s connect! You can find me on and . Twitter Dribbble Originally published at Alex Devero Blog .