admin管理员组

文章数量:1401849

I have a music player with an animated bar that displays the current position in the song. It is rendered with requestAnimationFrame and works by changing the width style of the div to X%, where X is the percentage of time through the current song.

This causes huge CPU use in Chrome I believe due to the constant reflow work being done each frame. What are other options I can use to eliminate reflows and reduce CPU?

Two other requirements: this web page is a web UI over a back end music server. It's not using any HTML5 media elements. As such, the page may be loaded when the song is already partially over, so the position will not always animate between 0 and 100.

The below fiddle shows up at about 30% CPU on my machine, which seems a bit high to animate a rectangle.

var pos = 0;
var s = document.getElementById('i');
f = function() {
  window.requestAnimationFrame(f);
  pos += .03;
  s.style.width = pos + '%';
}
f();
#i {
  background-color: red;
  position: absolute;
}
<div id="i">&nbsp;
</div>

I have a music player with an animated bar that displays the current position in the song. It is rendered with requestAnimationFrame and works by changing the width style of the div to X%, where X is the percentage of time through the current song.

This causes huge CPU use in Chrome I believe due to the constant reflow work being done each frame. What are other options I can use to eliminate reflows and reduce CPU?

Two other requirements: this web page is a web UI over a back end music server. It's not using any HTML5 media elements. As such, the page may be loaded when the song is already partially over, so the position will not always animate between 0 and 100.

The below fiddle shows up at about 30% CPU on my machine, which seems a bit high to animate a rectangle.

var pos = 0;
var s = document.getElementById('i');
f = function() {
  window.requestAnimationFrame(f);
  pos += .03;
  s.style.width = pos + '%';
}
f();
#i {
  background-color: red;
  position: absolute;
}
<div id="i">&nbsp;
</div>

Share Improve this question edited Feb 10, 2016 at 4:28 maddyblue asked Feb 10, 2016 at 3:54 maddybluemaddyblue 16.9k8 gold badges32 silver badges42 bronze badges 3
  • Why setAttribute? Just use s.style.width = pos + '%'; – Oriol Commented Feb 10, 2016 at 4:08
  • @Oriol changed to that; doesn't help any – maddyblue Commented Feb 10, 2016 at 4:10
  • @RayonDabre because I'm unaware of another way. – maddyblue Commented Feb 10, 2016 at 4:11
Add a ment  | 

6 Answers 6

Reset to default 4

There are a number of ways you could make a pure CSS progress bar that won’t cause a relayout, here are a few examples:

  1. animation - http://jsbin./yoqabe/edit?html,css,js,output

    I think one of the most performant ways would be to use an animation to control the background position of a linear-gradient. The only downside is that you can only play/pause the animation.

  2. background-position - http://jsbin./veyibe/edit?html,css,js,output

    If you need the ability to update the position with JS, then I would suggest updating the background-position of a gradient and applying CSS transitions, debouncing to avoid updating too quickly.

  3. translateX: http://jsbin./zolurun/edit?html,js,output

    You could also use CSS transforms to slide the progress bar inside of a container, which should also avoid a repaint.

These links might also be useful:

  • List of CSS layout, paint, and posite triggers: http://csstriggers.
  • Debounce info: https://davidwalsh.name/javascript-debounce-function

You can consider using other CSS properties which don't require layout opearations, such as background-size.

And use CSS animations instead of requestAnimationFrame.

var bar = document.getElementById('i');
function playSong(currentTime, duration) {
  bar.style.animationDuration = duration + 's';
  bar.style.animationDelay = - currentTime + 's';
}
playSong(3, 10);
#i {
  height: 1em;
  background-image: linear-gradient(red, red);
  background-repeat: no-repeat;
  animation: bar linear;
}
@keyframes bar {
  from { background-size: 0% 100%; }
  to { background-size: 100% 100%; }
}
<div id="i"></div>

If you use position: absolute or position: fixed on the progress bar itself, it should prevent large reflows on the page.

Use timeupdate, The time indicated by the element's currentTime attribute has changed.

Try this:

var audio = document.getElementById("audio");

function updateProgress() {
  var progress = document.getElementById("progress");
  var value = 0;
  if (audio.currentTime > 0) {
    value = Math.ceil((100 / audio.duration) * audio.currentTime);
  }
  progress.style.width = value + "%";
}


audio.addEventListener("timeupdate", updateProgress, false);
#progressBar {
  border: 1px solid #aaa;
  color: #fff;
  width: 295px;
  height: 20px;
}
#progress {
  background-color: #ff0000; // red
  height: 20px;
  display: block;
  height: 100%;
  width: 0;
}
<div id="progressBar"><span id="progress"></span>
</div>
<audio id="audio" controls>
  <source src="http://www.w3schools./tags/horse.ogg" type="audio/ogg" />
  <source src="http://www.w3schools./tags/horse.mp3" type="audio/mpeg" />Your browser does not support the audio element.
</audio>

The script you present is not very relevant to the one you desire, you animate on requestAnimationFrame but in reality you will animate every time the "song percentage" changes.

Assuming that you have a function (e.g. getSongPer()) that returns the current percentage of played song:

var oldPos, pos = 0;
var s = document.getElementById('i');
f = function() {
  oldPos = pos;
  pos = getSongPer();
  if(oldPos !== pos){
    s.style.width = pos + '%';
  }
  if(pos<100){
    window.requestAnimationFrame(f);
  }
}
f();

I didn't test it, but I expect it to be lighter, also, the performance will be affected by the precision of the percentage, e.g. there will be about 100 animation changes if you have zero digit precision and around ten times more for every digit after.

CSS:

#progress-bar {
  background-color: red;
  height: 10px;
  width: 100%;
  transform-origin: 0 0;
}

JS:

'use strict'

var progressBar = document.getElementById('progress-bar')

function setProgress(percentage) {
  requestAnimationFrame(function () {
    progressBar.style.transform = 'scaleX(' + percentage + '%)'
  })
}

setProgress(10)

When setting the width to 100% you get a full width colored bar.

Then we can apply the scale transform to set the width of the bar without reflowing.

But oh, it scales to the middle. We can fix that by setting the origin of the transform to the top left corner using transform-origin: x y, with x and y being 0.

Then we wrap the style change in requestAnimationFrame to let the browser optimize when to apply the change.

Bam! You have a performant zero reflow progress bar.

本文标签: javascriptChange width of element without reflowStack Overflow