Не работает параметр loop внутри timeline. Использую Anime.js и Vue

88
09 марта 2021, 14:20

Пытаюсь научится пользоватся AnimeJS

Не понимаю, как сделать таймлайн, в котором будет повторятся только отдельный промежуток.

Код:

leave: function (el, done) {
    var showLeave = anime.timeline({
        targets: el,
        easing: 'easeInOutCubic',
        duration: 600,
        complete: function() {
            done();
        }
    });
    showLeave
    .add({
        // targets: el,
        translateX: '15px',
        rotateZ: '50deg'
    })
    .add({
        rotateZ: '100deg',
        loop: 2,
        direction: 'reverse'
    })
    .add({
        rotateZ: '45deg',
        translateY: '30px',
        translateX: '30px',
        opacity: 0
    });
}

Во втором add необходимо, чтобы 2 раза покрутилось, но срабатывает только один раз. Повторение получается только повесить на весь таймлайн, но мне нужна только отдельная его часть.
Знаю, что это можно сделать более простым способом, например, через Animate.css. Но я во второй раз сталкиваюсь с подобной проблемой, и хотелось бы понять, как это делается.

Answer 1

Во втором add необходимо, чтобы 2 раза покрутилось, но срабатывает только один раз.

На самом деле, если использовать хуки loopBegin и loopComplete, которые вызываются при каждом цикле (loop) и вывести данные об изменениях (хотя бы в консоль), то можно убедиться, что анимация выполняется дважды.

// Отключим ненужные для примера 
// сообщения в консоли. 
Vue.config.productionTip = false; 
Vue.config.devtools = false; 
 
new Vue({ 
  el: '#app', 
  data: { 
    show: true 
  }, 
  methods: { 
    leave: function(el, done) { 
      console.clear(); 
 
      var loopBegan = 0; 
      var loopCompleted = 0; 
 
      var showLeave = anime.timeline({ 
        targets: el, 
        easing: 'easeInOutCubic', 
        duration: 600, 
        complete(anim) { 
          done(); 
          console.log('Все анимации выполнены:', el.style.transform); 
        } 
      }); 
 
      showLeave 
        .add({ 
          translateX: '15px', 
          rotateZ: '50deg', 
          complete: (anim) => console.log('Анимация 1 выполнена:', el.style.transform) 
        }) 
        .add({ 
          rotateZ: '100deg', 
          loop: 2, 
          direction: 'reverse', 
          loopBegin: (anim) => console.log('loop began:', ++loopBegan, anim.loop), 
          loopComplete: (anim) => console.log('loop completed:', ++loopCompleted, anim.loop), 
          complete: (anim) => console.log('Анимация 2 выполнена:', el.style.transform) 
        }) 
        .add({ 
          rotateZ: '45deg', 
          translateY: '30px', 
          translateX: '30px', 
          opacity: 0, 
          complete: (anim) => console.log('Анимация 3 выполнена:', el.style.transform) 
        }); 
    } 
  } 
});
*, 
*:before, 
*:after { 
  box-sizing: border-box; 
} 
 
.stamp { 
  display: inline-block; 
  max-width: 288px; 
  padding: 8px 12px; 
  box-sizing: border-box; 
  border: 1px solid #08c; 
  border-radius: 2px; 
  color: #80c; 
  background-color: #fff; 
  font-family: monospace; 
  font-size: 15px; 
  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, .2); 
} 
 
.stamp a { 
  color: #08c; 
}
<div id="app"> 
  <button @click="show = !show">{{ show ? 'Скрыть ...' : 'Показать ...' }}</button> 
  <hr> 
  <transition @leave="leave"> 
    <div v-if="show" class="stamp"> 
      Временные шкалы позволяют синхронизировать несколько анимаций вместе. По умолчанию каждая анимация, добавленная к временной шкале, начинается после окончания предыдущей анимации. <a href="https://github.com/juliangarnier/anime/issues/522" target="_blank">Loop behaviour in timeline</a> 
    </div> 
  </transition> 
</div> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> 
<script src="https://cdn.jsdelivr.net/npm/animejs@3.0.1/lib/anime.min.js"></script>

Параметры loop и direction не находятся в списке наследуемых свойств от родительского экземпляра временной шкалы timeline, в отличии от: targets, duration, delay, endDelay, round. Да и сама по себе временная шкала больше предназначена для манипуляции несколькими объектами, как мне кажется.

Но значение rotateZ второй анимацию можно разбить на несколько, и тем самым добиться нужного вам результата:

.add({
  // Массив значений: три поворота.
  rotateZ: [100, 50, 100],
  // На каждый поворот по 600 миллисекунд.
  duration: 1800,
})

// Отключим ненужные для примера 
// сообщения в консоли. 
Vue.config.productionTip = false; 
Vue.config.devtools = false; 
 
new Vue({ 
  el: '#app', 
  data: { 
    show: true 
  }, 
  methods: { 
    leave(el, done) { 
      const timeline = anime.timeline({ 
        targets: el, 
        easing: 'easeInOutCubic', 
        duration: 600, 
        complete(anim) { 
          done(); 
        } 
      }); 
 
      timeline.add({ 
          translateX: 15, 
          rotateZ: 50, 
        }) 
        .add({ 
          rotateZ: [100, 50, 100], 
          duration: 1800, 
        }) 
        .add({ 
          rotateZ: 45, 
          translateY: 30, 
          translateX: 30, 
          opacity: 0, 
        }); 
    } 
  } 
});
*, 
*:before, 
*:after { 
  box-sizing: border-box; 
} 
 
.stamp { 
  display: inline-block; 
  max-width: 288px; 
  padding: 8px 12px; 
  box-sizing: border-box; 
  border: 1px solid #08c; 
  border-radius: 2px; 
  color: #80c; 
  background-color: #fff; 
  font-family: monospace; 
  font-size: 15px; 
  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, .2); 
} 
 
.stamp a { 
  color: #08c; 
}
<div id="app"> 
  <button @click="show = !show">{{ show ? 'Скрыть ...' : 'Показать ...' }}</button> 
  <hr> 
  <transition @leave="leave"> 
    <div v-if="show" class="stamp"> 
      Параметры, установленные в родительском экземпляре временной шкалы, будут наследоваться всеми дочерними элементами: targets, duration, delay, endDelay, round. 
    </div> 
  </transition> 
</div> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> 
<script src="https://cdn.jsdelivr.net/npm/animejs@3.0.1/lib/anime.min.js"></script>

Ниже расположены еще два примера реализации анимации.

Первый основан на ключевых кадрах.

// Отключим ненужные для примера 
// сообщения в консоли. 
Vue.config.productionTip = false; 
Vue.config.devtools = false; 
 
new Vue({ 
  el: '#app', 
  data: { 
    show: true 
  }, 
  methods: { 
    leave(el, done) { 
      // Массив ключевых кадров. 
      const keyframes = [{ 
        // переместили вправо на 15 пикселей 
        translateX: 15, 
        // повернули на 50 градусов 
        rotateZ: 50, 
      }, { 
        // повернули от 50 до 100 
        rotateZ: 100, 
      }, { 
        // повернули от 100 до 50 
        rotateZ: 50, 
      }, { 
        // повернули от 50 до 100 
        rotateZ: 100, 
      }, { 
        // повернули от 100 до 45 
        rotateZ: 45, 
        // переместили вниз на 30 
        translateY: 30, 
        // переместили вправо от 15 до 30 
        translateX: 30, 
        // сделали прозрачным 
        opacity: 0, 
      }]; 
 
      // Время выполнения анимации. 
      // Каждому кадру отводим по 600 миллисекунд. 
      const duration = keyframes.length * 600; 
 
      anime({ 
        targets: el, 
        duration: duration, 
        keyframes: keyframes, 
        easing: 'easeInOutCubic', 
        complete(anim) { 
          done(); 
        } 
      }) 
 
      const defaultTweenSettings = { 
        duration: 1000, 
        delay: 0, 
        endDelay: 0, 
        easing: 'easeOutElastic(1, .5)', 
        round: 0 
      } 
    } 
  } 
});
*, 
*:before, 
*:after { 
  box-sizing: border-box; 
} 
 
.stamp { 
  display: inline-block; 
  max-width: 288px; 
  padding: 8px 12px; 
  box-sizing: border-box; 
  border: 1px solid #08c; 
  border-radius: 2px; 
  color: #80c; 
  background-color: #fff; 
  font-family: monospace; 
  font-size: 15px; 
  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, .2); 
} 
 
.stamp a { 
  color: #08c; 
}
<div id="app"> 
  <button @click="show = !show">{{ show ? 'Скрыть ...' : 'Показать ...' }}</button> 
  <hr> 
  <!-- 
      Лучше явным образом указывать v-bind:css="false" для переходов, 
      основанных только на JavaScript. 
      Это позволит Vue не тратить время на определение параметров CSS. 
      Кроме того, это убережёт нас от случайного взаимовлияния CSS-правил и JS-перехода. 
        --> 
  <transition @leave="leave" :css="false"> 
    <div v-if="show" class="stamp"> 
      Ключевые кадры анимации определяются с использованием массива в свойстве keyframes. 
    </div> 
  </transition> 
</div> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> 
<script src="https://cdn.jsdelivr.net/npm/animejs@3.0.1/lib/anime.min.js"></script>

Второй - тоже на ключевых кадрах, но без применения библиотеки Anime.js.

// Отключим ненужные для примера 
// сообщения в консоли. 
Vue.config.productionTip = false; 
Vue.config.devtools = false; 
 
new Vue({ 
  el: '#app', 
  data: { 
    show: true 
  } 
});
*, 
*:before, 
*:after { 
  box-sizing: border-box; 
} 
 
.stamp { 
  display: inline-block; 
  max-width: 288px; 
  padding: 8px 12px; 
  box-sizing: border-box; 
  border: 1px solid #08c; 
  border-radius: 2px; 
  color: #80c; 
  background-color: #fff; 
  font-family: monospace; 
  font-size: 15px; 
  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, .2); 
} 
 
.stamp a { 
  color: #08c; 
} 
 
.avulsion-leave {} 
 
.avulsion-leave-active { 
  animation-name: avulsion-in; 
  animation-delay: 0s; 
  animation-duration: 3s; 
  animation-timing-function: ease; 
} 
 
@keyframes avulsion-in { 
  from { 
    /** 
     * Задаем начальное состояние. 
     * Его можно также задать в .avulsion-leave {} 
     */ 
    transform: translateX(0) rotateZ(0); 
  } 
  20% { 
    /** 
     * переместили вправо на 15 пикселей; 
     * повернули на 50 градусов. 
     */ 
    transform: translateX(15px) rotateZ(50deg); 
  } 
  40% { 
    /** 
     * повернули от 50 до 100. 
     */ 
    transform: rotateZ(100deg); 
  } 
  60% { 
    /** 
     * повернули от 100 до 50. 
     */ 
    transform: rotateZ(50deg); 
  } 
  80% { 
    /** 
     * повернули от 50 до 100; 
     * задали кадру свойство `opacity`, 
     * т.к. на следующем это свойство изменится. 
     */ 
    transform: rotateZ(100deg); 
    opacity: 1; 
  } 
  to { 
    /** 
     * повернули от 100 до 45; 
     * переместили вправо от 15 до 30; 
     * переместили вниз на 30; 
     * сделали прозрачным. 
     */ 
    transform: rotateZ(45deg) translateX(30px) translateY(30px); 
    opacity: 0; 
  } 
} 
 
 
/** 
 * Анимация появления элемента. 
 */ 
 
.avulsion-enter-active { 
  animation-name: slidein; 
  animation-duration: .4s; 
  animation-delay: 0s; 
  animation-timing-function: ease-in-out; 
} 
 
@keyframes slidein { 
  from { 
    margin-left: -288px; 
  } 
  to { 
    margin-left: 0; 
  } 
}
<div id="app"> 
  <button @click="show = !show">{{ show ? 'Скрыть ...' : 'Показать ...' }}</button> 
  <hr> 
  <transition name="avulsion"> 
    <div v-if="show" class="stamp"> 
      v-enter-active и v-leave-active дают возможность указать различные анимационные эффекты для переходов появления и исчезновения элемента. CSS-анимации v-enter удаляется только при наступлении события animationend. 
    </div> 
  </transition> 
</div> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> 
<script src="https://cdn.jsdelivr.net/npm/animejs@3.0.1/lib/anime.min.js"></script>

READ ALSO
При нажатии на мышь срабатывают сразу несколько событий из-за анимации (canvas игра)

При нажатии на мышь срабатывают сразу несколько событий из-за анимации (canvas игра)

Я уже задавал вопрос по этому проекту (В какой-то момент, отскакивающий от краёв канваса мяч, попадает в баг и ведет себя аномально), код тот...

105
Не срабатывает window.open

Не срабатывает window.open

Простая функция, которой передаётся объект blob:

92
Не работает форма для регистрации

Не работает форма для регистрации

Есть форма для регистрации, но про нажатии на кнопку ничего не происходитКод формы:

81
Создание CSS3 эффекта мигающего века

Создание CSS3 эффекта мигающего века

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

100