Introduction
Not long ago, I was on StackOverflow and I ran across a question asking if anyone knew of a library or how to implement Youtube’s double tap to rewind and forward feature in a browser. It’s a feature on the Youtube mobile application app that allows the user to forward the video if the right side of the video is double tapped and rewind the video if the left side is double tapped. It’s a nifty feature that I didn’t know existed before I saw the question since I don’t use Youtube on my phone much but I thought it would be cool to build with JavaScript. In this post, I will demonstrate how to implement such a feature using HTML, CSS, and vanilla JavaScript.
The Objective
I explained the basic functionality in the introduction, but I think it will be helpful to have a visual example of what we are building. I’ve posted two gifs below which demonstrate the double tap feature of the Youtube mobile application on an iPhone. In the visual aid, you will see that two taps on the left of the screen will rewind the video ten seconds and two taps to the right of the screen will fast-forward the video ten seconds. Additionally, for every consecutive double tap, the effect is cumulative and the amount of seconds to forward or rewind is multiplied. For example, three double taps to the right of the video will forward the video ahead thirty seconds. Our version of this feature will work similarly except it will seek on double click instead of a double tap since we will be developing this for the web.
The Structure
The structure will be fairly simple. Deconstructing the feature, we can break the elements down to the video player, (2) the rewind UI (user interface) indicator, and the forward UI indicator. I will nest all these elements inside a div with the class name of “video-player.” We’ll add an HTML5 video player, and have a few divs for the forward and rewind indicators. It’s important to note that the video player has it’s built-in controls disabled since double tapping a video element will cause the video to enter the fullscreen mode.
<div class="video-container">
<video class="player__video viewer" src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"></video>
<div class="video-rewind-notify rewind notification">
<div class="rewind-icon icon">
<i class="left-triangle triangle">◀◀◀</i>
<span class="rewind">10 seconds</span>
</div>
</div>
<div class="video-forward-notify forward notification">
<div class="forward-icon icon">
<i class="right-triangle triangle">▶▶▶</i>
<span class="forward">10 seconds</span>
</div>
</div>
</div>
The Styles
I’ll add some basic styles to the video player. The main thing that will be styled is the ripple effect when a user double taps the video and displays how much the video will be rewound or forwarded. Using CSS animations I can replicate the ripple effect that YouTube has. The rest of the styles will be to position the video for purposes of the demo. I’ve just created some quick and dirty styling that you can grab from the Codepen. I’ll go more in detail in this post about the Javascript.
The JavaScript
The fun part of implementing the YouTube double tap to rewind/forward will be the JavaScript. Everything has been set up, now we can get the feature working!
The first thing we will do is grab the DOM elements we will need to manipulate. For our purposes that will be (1) the HTML5 video element, (2) the forward and rewind ripple notifications which are the visual indicators of if a double click has occurred, and (3) the span elements which show the rate at which the video will be rewound and forwarded.
//grab the video dom element
const video = document.querySelector('video');
const notifications = document.querySelectorAll('.notification');
const forwardNotificationValue = document.querySelector('.video-forward-notify span');
const rewindNotificationValue = document.querySelector('.video-rewind-notify span');
Double Click
Next, we want to detect if the user double-clicks the left and right side of the video element. An event handler for double click should be added to the video player DOM element. I found it hard to visually distinguish between a double click and a single click since a single click is required for the double click. So we’ll add an additional event handler to the video on click that will toggle the pause and play actions on the video.
function togglePlay(){
video.paused ? video.play() : video.pause();
}
//Event Listeners
video.addEventListener('click', togglePlay);
Now that the toggle play has been added, we can move towards handling the double click event. First, we will create a function that will be the callback for the double click event handler that detects if the left side of the video was clicked or the right side. We’ll pass the function to the handler. From the event, we can use offsetX
to help determine what side of the video is clicked. It will give us the X coordinate of the mouse pointer at the time of the double click, relative to the video. We can take the value of the X coordinate and compare it to the width of the video halved (video.offsetWidth/2
). If the X coordinate is less than half of the video’s width then we know the left side of the video was clicked. However, if the X coordinate is greater than half the width of the video then we can assume the right side of the video was clicked. So I’ll create two more functions called forwardVideo()
and rewindVideo()
and call them accordingly, based on the logic I’ve defined above.
function forwardVideo(){
}
function rewindVideo(){
}
//Event Handlers
function doubleClickHandler(e){
const videoWidth = video.offsetWidth;
(e.offsetX < videoWidth/2) ? rewindVideo() : forwardVideo();
}
video.addEventListener('dblclick', doubleClickHandler);
We will leave the forwardVideo()
and rewindVideo()
functions empty for now and work on a function to handle the forwarding and rewinding of the video element. It will be called updateCurrentTime
. It will take one parameter, which will be the number of seconds to rewind or forward the video. We will also want to define variables outside the function which will be accumulators that keeps track of how far the user wants to forward or rewind based on their clicks (forwardSpeed
and rewindSpeed
), and declare a variable named timer
to reference a setTimeout function we will use to track consecutive double clicks.
let timer;
let rewindSpeed = 0;
let forwardSpeed = 0;
function updateCurrentTime(delta){
}
Seeking
Let’s get building within the function. Since the parameter is the number of seconds to forward or rewind we can detect what the user intends to do by seeing if the variable delta is less than or greater than zero. If it is less than zero then we know the user is rewinding, and if greater than zero then it is the case the user intends to forward the video. With this information, we can calculate the number of seconds to forward or rewind the video using our accumulator variables and the delta variable. We will simply increment the accumulator variables by the delta. If the video is rewinding we will set the forwardSpeed
variable which the amount to change the video to zero since the variable no longer needs to be incremented because the user is not consecutively clicking the left side of the video and vice versa.
let timer;
let rewindSpeed = 0;
let forwardSpeed = 0;
function updateCurrentTime(delta){
let isRewinding = delta < 0;
if(isRewinding){
rewindSpeed = rewindSpeed + delta;
forwardSpeed = 0;
}else{
forwardSpeed = forwardSpeed + delta;
rewindSpeed = 0;
}
}
Based on this information we will update the current time of the video by incrementing it by the forward or rewind speed. In addition, we will update the value on the UI indicator that tells the user how many seconds the video is being forwarded or rewound as well. Basically, we will update the innerHTML on the element and take the absolute value of the speed and add it’s value to a template literal.
let timer;
let rewindSpeed = 0;
let forwardSpeed = 0;
function updateCurrentTime(delta){
let isRewinding = delta < 0;
if(isRewinding){
rewindSpeed = rewindSpeed + delta;
forwardSpeed = 0;
}else{
forwardSpeed = forwardSpeed + delta;
rewindSpeed = 0;
}
let speed = (isRewinding ? rewindSpeed : forwardSpeed);
video.currentTime = video.currentTime + speed;
let NotificationValue = isRewinding ? rewindNotificationValue : forwardNotificationValue ;
NotificationValue.innerHTML = `${Math.abs(speed)} seconds`;
}
To help us detect if there are consecutive double clicks we will use the setTimeout
function. Essentially whenever the updateCurrentTime
function is called on the double click we are incrementing the speed to forward or rewind the video by incrementing the rewindSpeed
and forwardSpeed
speeds by the delta
variable. We will make it so that within two seconds the forwardSpeed
and rewindSpeed
will be set to zero. The variable timer
will be set to the setTimeOut
function.
let timer;
let rewindSpeed = 0;
let forwardSpeed = 0;
function updateCurrentTime(delta){
let isRewinding = delta < 0;
if(isRewinding){
rewindSpeed = rewindSpeed + delta;
forwardSpeed = 0;
}else{
forwardSpeed = forwardSpeed + delta;
rewindSpeed = 0;
}
let speed = (isRewinding ? rewindSpeed : forwardSpeed);
video.currentTime = video.currentTime + speed;
let NotificationValue = isRewinding ? rewindNotificationValue : forwardNotificationValue ;
NotificationValue.innerHTML = `${Math.abs(speed)} seconds`;
//reset accumulator within 2 seconds of a double click
timer = setTimeout(function(){
rewindSpeed = 0;
forwardSpeed = 0;
}, 2000); // you can edit this delay value for the timeout, i have it set for 2 seconds
}
The last part of this step is to add a clearTimeOut right before we update the video. This way the variables forwardSpeed
and rewindSpeed
will not be reset to zero on consecutive double clicks and will instead be incremented by the delta
variable.
let timer;
let rewindSpeed = 0;
let forwardSpeed = 0;
function updateCurrentTime(delta){
let isRewinding = delta < 0;
if(isRewinding){
rewindSpeed = rewindSpeed + delta;
forwardSpeed = 0;
}else{
forwardSpeed = forwardSpeed + delta;
rewindSpeed = 0;
}
//clear the timeout
clearTimeout(timer);
let speed = (isRewinding ? rewindSpeed : forwardSpeed);
video.currentTime = video.currentTime + speed;
let NotificationValue = isRewinding ? rewindNotificationValue : forwardNotificationValue ;
NotificationValue.innerHTML = `${Math.abs(speed)} seconds`;
//reset accumulator within 2 seconds of a double click
timer = setTimeout(function(){
rewindSpeed = 0;
forwardSpeed = 0;
}, 2000); // you can edit this delay value for the timeout, i have it set for 2 seconds
}
The updateCurrentTime
function is now fully built out. We will call it in the forwardVideo
and rewindVideo
functions. On the forward function, updateCurrentTime
will take a parameter of 10 since we want to increment by 10 seconds and -10 on the rewind function since we want to decrement by 10 seconds.
function forwardVideo(){
updateCurrentTime(10);
}
function rewindVideo(){
updateCurrentTime(-10);
}
Adding the Animations
The functionality of double-clicking to forward and rewind is working now! But we need to do one more thing, which is to animate an indicator in to show that the video’s current time is updating. Additionally, we need to animate the indicator out. We already have a CSS animations added to a class on the notification DOM elements called animate-in
. When added to a notification DOM element it will fade the notification in and when the class is removed it will fade it out.
So let’s create two functions, one to animate the notifications in and one to indicate the notifications out. We will name them animateNotificationIn
and animateNotificationOut
. The animateNotificationIn
will take one parameter which will be a boolean that tells the function if the video is being rewound. The animateNotificationOut
function will be put on an event listener and will be called when a notification DOM element’s CSS animation ends.
function animateNotificationIn(isRewinding){
}
function animateNotificationOut(){
}
In animateNotificationIn
, if the user wants to rewind the video we will grab the rewind notification DOM element (which is at index 0 of notifications
) and add the class animateIn
to the element. Otherwise, we will grab the forward notification DOM element (which is at index 1 of notifications
) and add the class animateIn
to the element.
function animateNotificationIn(isRewinding){
isRewinding ? notifications[0].classList.add('animate-in') : notifications[1].classList.add('animate-in');
}
For the animateNotificationOut
since we are adding it on an event listener we will just reference this
for the element within the function and remove the animateIn
class.
function animateNotificationOut(){
this.classList.remove('animate-in');
}
notifications.forEach(function(notification){
notification.addEventListener('animationend', animateNotificationOut);
});
Finally we will call the animateNotificationIn
function in both the forwardVideo
and rewindVideo
functions. The call to animateNotificationIn
in forwardVideo
will have a parameter of false, denoting we want to forward the video. The call to animateNotificationIn
in rewindVideo
will have a parameter of true, denoting we want to forward the video. On the notification DOM elements that we grabbed earlier we will add the function to an animationend
event listener.
function forwardVideo(){
updateCurrentTime(10);
animateNotificationIn(false);
}
function rewindVideo(){
updateCurrentTime(-10);
animateNotificationIn(true);
}
Wrapping it up
Voila! We have replicated similar functionality to Youtube’s video play on its mobile application! A demo of the functionality can be seen below. When we double tap the left side of the screen the video is rewound and if the right side is double tap the video is forwarded. In addition, consecutive taps increment the amount the video will be forwarded or rewound. If you are interested in seeing the complete source code for this tutorial, you can find it on CodePen titled “HTML5 Video Double Click to Seek”.