Получить/установить текущий процентный/ключевой кадры

Можно ли установить/установить текущий процент анимации анимации CSS3 @keyframes с помощью javascript, jQuery или некоторых других средств?

Скажем, например, существует div с классом .spin, который просто крутится вокруг по кругу, используя ключевой кадр, также называемый spin.

  • Я попытался получить текущее процентное значение анимации с помощью $('.spin').css('animation'), $('.spin').css('animation: spin') и еще пару способов, но все они оповещают о пустом

  • Я также заинтересован в изменении исходной анимации на каждом предопределенном ключевом кадре%, и я пробовал такие вещи, как $('.spin').css('animation', '..new definition here...') и $('.spin').css('spin', '50%', '...new definition here...), безрезультатно.

Любые идеи?

Ответ 1

Я достиг примерно того, что хотел, используя чистый javascript с моим CSS3.

Для моего эксперимента, чтобы найти способ сделать эти цели, я создал базовую анимацию CSS3, вращающую круг вокруг небольшого кругового пути. Моя цель состояла в том, чтобы при нажатии кнопки изменить происхождение анимации на новое местоположение

Чтобы достичь первой цели получения процентного значения анимации, я просто аппроксимировал текущий процент, используя следующий setInterval, который отображает приблизительный текущий процент. Это выполняется каждые 40 миллисекунд, чтобы соответствовать длительности анимации (milliseconds / 100)

var result = document.getElementById('result'), currentPercent = 0;
var showPercent = window.setInterval(function() {
  if(currentPercent < 100)
  {
    currentPercent += 1;
  }
  else {
    currentPercent = 0;
  }
  result.innerHTML = currentPercent;
}, 40);

Примечание по этому решению:

  • Это не идеально, потому что счетчик продолжает работать при нажатии другой вкладки, но анимация останавливается, поэтому они становятся несинхронизированными.
  • Он также ошибочен, когда кнопка нажата долго после последнего щелчка. Очевидно, что setInterval работает немного дольше, чем анимация, поэтому они становятся все меньше и меньше синхронизируются с каждой итерацией.
  • Я искал повсюду для более совершенного решения, но не смог придумать один из них.

Чтобы достичь второй цели установки нового определения для значения анимации%, потребовалось немного более сложное решение. Выполнив десятки статей и веб-страниц (важные из них перечислены ниже), мне удалось найти следующее решение:

var circle = document.getElementById('circle'), 
    button = document.getElementById('button');
var result = document.getElementById('result'), 
    totalCurrentPercent = 0,
    currentPercent = 0;
var showPercent = window.setInterval(function() {
  if(currentPercent < 100)
  {
    currentPercent += 1;
  }
  else {
    currentPercent = 0;
  }
  result.innerHTML = currentPercent;
}, 40);
function findKeyframesRule(rule) {
    var ss = document.styleSheets;
    for (var i = 0; i < ss.length; ++i) {
        for (var j = 0; j < ss[i].cssRules.length; ++j) {
            if (ss[i].cssRules[j].type == window.CSSRule.WEBKIT_KEYFRAMES_RULE && ss[i].cssRules[j].name == rule) { return ss[i].cssRules[j]; }
        }
    }
    return null;
}
function change(anim) {
  var keyframes = findKeyframesRule(anim),
      length = keyframes.cssRules.length;
  var keyframeString = [];  
  for(var i = 0; i < length; i ++)
  {
    keyframeString.push(keyframes[i].keyText);
  }
  var keys = keyframeString.map(function(str) {
    return str.replace('%', '');
  });
  totalCurrentPercent += currentPercent;
  if(totalCurrentPercent > 100)
  {
    totalCurrentPercent -= 100;
  }
  var closest = getClosest(keys);  
  var position = keys.indexOf(closest), 
      firstPercent = keys[position];
  for(var i = 0, j = keyframeString.length; i < j; i ++)
  {
    keyframes.deleteRule(keyframeString[i]);
  }
  var multiplier = firstPercent * 3.6;
  keyframes.insertRule("0% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 0) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 0) + "deg); background-color:red; }");
  keyframes.insertRule("13% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 45) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 45) + "deg); }");
  keyframes.insertRule("25% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 90) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 90) + "deg); }");
  keyframes.insertRule("38% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 135) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 135) + "deg); }");
  keyframes.insertRule("50% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 180) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 180) + "deg); }");
  keyframes.insertRule("63% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 225) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 225) + "deg); }");
  keyframes.insertRule("75% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 270) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 270) + "deg); }");
  keyframes.insertRule("88% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 315) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 315) + "deg); }");
  keyframes.insertRule("100% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 360) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 360) + "deg); }");
  circle.style.display = "inherit";
  circle.style.webkitAnimationName = anim; 
  window.clearInterval(showPercent);
  currentPercent = 0;
  showPercent = self.setInterval(function() {
    if(currentPercent < 100)
    {
      currentPercent += 1;
    }
    else {
      currentPercent = 0;
    }
    result.innerHTML = currentPercent;
  }, 40); 
}
button.onclick = function() {
    circle.style.webkitAnimationName = "none";
    circle.style.display = "none";
    setTimeout(function () { 
        change("rotate"); 
    }, 0);
}
function getClosest(keyframe) {
  var curr = keyframe[0];
  var diff = Math.abs (totalCurrentPercent - curr);
  for (var val = 0; val < keyframe.length; val++) {
    var newdiff = Math.abs(totalCurrentPercent - keyframe[val]);
    if (newdiff < diff) {
      diff = newdiff;
      curr = keyframe[val];
     }
  }
  return curr;
}

Проверьте сам эксперимент здесь, включая комментарии, описывающие, что делает каждая часть javascript

Примечания к этому решению:

  • Я попытался сделать функцию изменения как не жестко закодированную, насколько возможно
  • Он работает хорошо для аппроксимации текущего @keyvalue процента
  • Переход от одной анимации к другой только такой же гладкий, как если бы все близкие значения% анимации соответствовали текущему% анимации, поэтому добавление более% определений в анимацию сделает ее еще более гладкой.

В процессе попытки найти решение проблемы, я нашел эти полезные ресурсы:

  • Ответ RussellUresti в этом сообщении SO и соответствующий пример был весьма влиятельным и очень помог моему окончательному решению.
  • Чтобы получить самое близкое значение на основе значений ввода и массива, я использовал метод paxdiablo в этой SO post (спасибо)
  • Этот плагин, в то время как я сам не использовал его, казалось, достиг очень похожего (хотя, казалось бы, не совсем настраиваемого) эффекта в jQuery

--- --- EDIT

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

Примечание. !important в атрибуте CSS изменился, если у вас нет более ровного селектора для класса анимации, чем селектор jQuery, иначе как div #defaultID.animationClass {, а не только #defaultID.animationClass {. Поскольку #defaultID и #defaultID.animationClass оба уровня, для этого примера требуется !important

- другое редактирование -

Для получения дополнительной информации по этой теме, мой пост по CSS-трюкам