So I've made a simple custom progress bar for an audio object within my website. The progress bar works fine, but I've noticed it is very choppy. I also noticed that on websites such as Facebook and YouTube, their progress bar transitions seem to be exceptionally smooth (watch any video and you'll see what I mean).
I thought a workaround to this might be to use some crafty JavaScript and CSS, but in the end it just seemed very tacky, CPU heavy for no reason and looked essentially exactly the same as before. (This is what I came up with):
setInterval(function(){
var rect = elapsedContainer.getBoundingClientRect();
var percentage = audio.currentTime / audio.duration;
elapsed.style.width = (percentage * rect.width) + "px";
}, 33); // 30fps
.elapsed-container{
width: 100%;
height: 10px;
background: grey;
}
.elapsed{
left: 0;
height: 100%;
background: red;
transition: width 33ms linear;
}
JsFiddle
All help is appreciated, cheers.
So I've made a simple custom progress bar for an audio object within my website. The progress bar works fine, but I've noticed it is very choppy. I also noticed that on websites such as Facebook and YouTube, their progress bar transitions seem to be exceptionally smooth (watch any video and you'll see what I mean).
I thought a workaround to this might be to use some crafty JavaScript and CSS, but in the end it just seemed very tacky, CPU heavy for no reason and looked essentially exactly the same as before. (This is what I came up with):
setInterval(function(){
var rect = elapsedContainer.getBoundingClientRect();
var percentage = audio.currentTime / audio.duration;
elapsed.style.width = (percentage * rect.width) + "px";
}, 33); // 30fps
.elapsed-container{
width: 100%;
height: 10px;
background: grey;
}
.elapsed{
left: 0;
height: 100%;
background: red;
transition: width 33ms linear;
}
JsFiddle
All help is appreciated, cheers.
Share Improve this question edited Sep 3, 2019 at 8:09 GROVER. asked Sep 3, 2019 at 7:48 GROVER.GROVER. 4,3882 gold badges31 silver badges75 bronze badges 2- 3 Could you please convert the code into snippet so we will be able to run it. Or paste the link to some codepen or sandbox. Thanks – Artem Arkhipov Commented Sep 3, 2019 at 7:56
- Sure, but I mean it's pretty self explanatory. – GROVER. Commented Sep 3, 2019 at 7:58
4 Answers
Reset to default 4You can try using window.requestAnimationFrame()
instead of setTimeout()
. The requestAnimationFrame
callback allows the puter to try to get as close to 60fps as possible, but can alter the framerate for load, making it more performant than setTimeout()
, which has to always match the specified framerate and can then end up skipping frames and then 'flicker' (see here for more info).
I also removed the CSS transition, so you are not mixing animations (since the requestAnimationFrame already animates at 60fps, the CSS transition is somewhat irrelevant)
// Change setTimeout to requestFrameAnimation
function progress_animation() {
var rect = container.getBoundingClientRect();
var percentage = audio.currentTime / audio.duration;
elapsed.style.width = (percentage * rect.width) + "px";
window.requestAnimationFrame(progress_animation);
};
// Only run animation when relevant
document.getElementById("play").onclick = function(){
window.requestAnimationFrame(progress_animation);
audio.play();
}
https://jsfiddle/4ymch2jg/
This appears smoother for me at least
Idea is to use requestAnimationFrame instead of simple setInterval. You may read more about it here. Main thins is that this method optimizes browsers render possibilities and resources usage. Thats why it looks much more smooth.
Here is the code example which you then can extend as you need.
let duration = 20000;
let startTime = Date.now();
const elapsedContainer2 = document.querySelector('#con-2');
const elapsed2 = document.querySelector('#el-2');
function animate() {
var rect = elapsedContainer2.getBoundingClientRect();
let percentage = (Date.now()-startTime) / duration;
elapsed2.style.width = (percentage * rect.width) + "px";
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Here is the link to the codepen where you can see the difference between both approaches. By the way, the more loaded your machine is the more difference is noticeable.
Note, that I've also changed the css animation from left 33ms
in your case to the width 0.16s
. In the code we change the width of the div, not the left property, so it should be mentioned in the CSS animation. 0.16s
is close to 60fps which is exactly what requestAnimationFrame
is trying to achieve.
.elapsed{
left: 0;
height: 100%;
background: red;
transition: width 0.16s linear;
}
You can add artificial smooth using an moving average on 10 percent.
https://en.m.wikipedia/wiki/Moving_average
It mean you calcul the average speed on the last 10 percent of your slide bar and show an estimation using speed = distance / time
You have to wait for the css transition to end before you set new width. So you need to use transitionend event. Here is one example:
var percentDone = 0;
var elapsed = document.getElementsByClassName("elapsed")[0];
setProgress = function() {
percentDone++;
if( percentDone<=100 ) {
elapsed.style.width = percentDone + "%";
};
};
elapsed.addEventListener('transitionend', setProgress);
setProgress();
.elapsed-container{
width: 100%;
height: 10px;
background: grey;
}
.elapsed{
left: 0;
width: 0;
height: 100%;
background: red;
transition: width 33ms linear;
}
<div class="elapsed-container">
<div class="elapsed"></div>
</div>
You can see it working on JSFiddle