Изменить ширину элемента без перекомпоновки

У меня есть музыкальный проигрыватель с анимированной полосой, отображающей текущую позицию в песне. Он визуализируется с помощью requestAnimationFrame и работает путем изменения стиля width элемента div на X%, где X — это процент времени в текущей песне.

Я полагаю, что это вызывает огромную загрузку ЦП в Chrome из-за постоянной работы по перекомпоновке, выполняемой в каждом кадре. Какие другие варианты я могу использовать, чтобы устранить перекомпоновки и снизить нагрузку на ЦП?

Два других требования: эта веб-страница представляет собой веб-интерфейс над фоновым музыкальным сервером. Он не использует никаких мультимедийных элементов HTML5. Таким образом, страница может быть загружена, когда песня уже частично закончилась, поэтому позиция не всегда будет анимироваться между 0 и 100.

Приведенная ниже скрипка показывает примерно 30% загрузки процессора на моей машине, что кажется немного большим для анимации прямоугольника.

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>


person mjibson    schedule 10.02.2016    source источник
comment
Почему setAttribute? Просто используйте s.style.width = pos + '%';   -  person Oriol    schedule 10.02.2016
comment
@Oriol изменился на это; не помогает никак   -  person mjibson    schedule 10.02.2016
comment
@RayonDabre, потому что я не знаю другого пути.   -  person mjibson    schedule 10.02.2016


Ответы (6)


Есть несколько способов сделать полосу прогресса на чистом CSS, которая не вызовет ретрансляции, вот несколько примеров:

  1. animationhttp://jsbin.com/yoqabe/edit?html,css,js,output

    Я думаю, что одним из наиболее эффективных способов было бы использование анимации для управления фоновым положением линейного градиента. Единственным недостатком является то, что вы можете только воспроизводить/приостанавливать анимацию.

  2. background-positionhttp://jsbin.com/veyibe/edit?html,css,js,output

    Если вам нужна возможность обновить позицию с помощью JS, то я бы предложил обновить background-position градиента и применить переходы CSS, устраняя дребезг, чтобы избежать слишком быстрого обновления.

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

    Вы также можете использовать преобразования CSS для перемещения индикатора выполнения внутри контейнера, что также должно избежать перерисовки.

Эти ссылки также могут быть полезны:

person Ted Whitehead    schedule 10.02.2016

Вы можете рассмотреть возможность использования других свойств CSS, которые не требуют операций с макетом, таких как background-size.

И используйте анимацию CSS вместо 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>

person Oriol    schedule 10.02.2016
comment
Что делать, если песня уже наполовину готова? Мне нужно, чтобы полоса начиналась с 50%, а не переходила от 0 до 100 за половину времени. (Песня не воспроизводится на вкладке, вкладка — это просто пользовательский интерфейс над музыкальным сервером.) - person mjibson; 10.02.2016
comment
@mjibson Для этого вы можете использовать свойства анимации. - person Oriol; 10.02.2016

Если вы используете position: absolute или position: fixed на самом индикаторе выполнения, это должно предотвратить большие перекомпоновки на странице.

person michael    schedule 10.02.2016
comment
Тогда на вашей странице должно быть что-то еще, что вызывает это. Какой JS вы используете? У меня такое ощущение, что в вашем JS есть что-то интенсивное, что вызывает отставание, которое вы испытываете, а не просто изменение размера элемента. - person michael; 10.02.2016

Используйте timeupdate. Время, указанное атрибутом currentTime элемента, изменилось. .

Попробуй это:

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.com/tags/horse.ogg" type="audio/ogg" />
  <source src="http://www.w3schools.com/tags/horse.mp3" type="audio/mpeg" />Your browser does not support the audio element.
</audio>

person Rayon    schedule 10.02.2016
comment
timeupdate работает только с аудиоэлементами HTML5, что мне не нравится. Веб-страница представляет собой пользовательский интерфейс на внутреннем музыкальном сервере. - person mjibson; 10.02.2016

Сценарий, который вы представляете, не очень соответствует тому, который вы хотите, вы анимируете requestAnimationFrame, но на самом деле вы будете анимировать каждый раз, когда изменяется «процент песни».

Предполагая, что у вас есть функция (например, getSongPer()), которая возвращает текущий процент воспроизведенной песни:

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();

Я не тестировал его, но ожидаю, что он будет легче, кроме того, на производительность будет влиять точность процента, например. будет около 100 изменений анимации, если у вас есть нулевая точность цифр, и примерно в десять раз больше для каждой цифры после.

person xpy    schedule 11.02.2016

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)

При установке ширины на 100% вы получаете цветную полосу полной ширины.

Затем мы можем применить масштабное преобразование, чтобы установить ширину полосы без перекомпоновки.

Но о, он масштабируется до середины. Мы можем исправить это, установив начало преобразования в верхний левый угол, используя transform-origin: x y, где x и y равны 0.

Затем мы оборачиваем изменение стиля в requestAnimationFrame, чтобы позволить браузеру оптимизировать применение изменения.

Бам! У вас есть эффективная полоса прогресса нулевого перекомпоновки.

person user3654410    schedule 08.03.2018
comment
Вы должны предоставить больше информации с вашим ответом. Возможно, объяснение того, что вы делаете. Простое размещение ответа приведет к тому, что ваше сообщение будет отклонено или удалено. - person Chris; 08.03.2018
comment
@Крис Лучше? :) - person user3654410; 10.03.2018