Введение в Vue.js: Анимации

26.09.2017

Это пятая, заключительная часть в серии статей о JavaScript фреймворке Vue.js. В этой последней статье мы рассмотрим анимацию (если вы здесь, то должны понимать о чем идет речь). Это не полное руководство, а скорее для разбор основ, которые помогут вам узнать Vue.js и понять, что вам может предложить этот фреймворк.

Серия статей

  1. Рендеринг, директивы и события
  2. Components, Props и Slots
  3. Vue-cli
  4. Vuex 
  5. Анимации (Вы здесь)

Немного лора

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

Мы начнем с обсуждения CSS переходов и анимаций, а после поговорим о JS Animation Hooks и о анимации с помощью методов Lifecycle. Состояния переходов выходят за рамки этой статьи. Но вот достаточно прокомментированный пен, который вполне объясняет их. Вероятно, я напишу статью об этом, как только высплюсь.

Что лучше: переходы или анимации?

Вероятно вы удивитесь тем, что переходы и анимации находятся в разных разделах, но позвольте мне объяснить, что они разные, хоть и похожи. Переход в основном работает путем интерполяции значений из одного состояния в другое. Мы можем делать с ними классные вещи, но они довольно просты. Отсюда туда и обратно.

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

Что касается инструментов, то они оба полезны. Просто переходы это как пила, а анимации, как бензопила 🙂 Иногда просто нужно распилить что то одно, и было бы глупо идти покупать дорогое оборудование ради этого. Однако для сложных проектов имеет смысл делать капитальные вложения.

Теперь, когда у нас есть эти основы, давайте поговорим о Vue!

CSS Переходы

Допустим, у нас простое модальное окно. Оно появляется и исчезает по клику на кнопку. Основываясь на предыдущих статьях, мы уже знаем, что мы можем: создать экземпляр Vue с кнопкой, создать дочерний компонент из этого экземпляра, установить данные в таком состоянии, чтобы он переключил булевские значения и добавил обработчик событий для отображения и скрытия этого дочернего компонента. Можно было бы использовать v-if или v-show для переключения видимости. Или даже использовать слот для переключения кнопки.

 

Let’s trigger this here modal!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Child = {
  template: '#childarea'
};
 
new Vue({
  el: '#app',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  },
  components: {
    appChild: Child
  }
});

See the Pen Базовое демо переходов без переходов 😀 by RinaMina (@FurkMe) on CodePen.

Это работает, но довольно странно, так как это окно просто выскакивает нам прямо в лицо. ?

Мы уже подключали и отключали этот дочерний компонент с помощью v-if, поэтому Vue позволит нам отслеживать изменения этого события, если мы завершим это условие в компоненте перехода:

1
2
3
    <button>
      Close
    </button>

Теперь мы можем просто использовать transition из коробки. Это даст нам префикс v- для некоторых хуков перехода, которые мы можем использовать в нашем CSS. Он предложит enter/leave позицию, с которой начинается анимация в первом кадре, enter-active/leave-active во время анимации и enter-to/leave-to, которой заканчивается анимация.

Я покажу схему, которая описывает это настолько красиво и ясно, насколько это возможно:

Лично я обычно не работаю с префиксом v- по умолчанию. Я всегда даю переходу имя, чтобы не было конфликтов, если я захочу, в конце концов, применить другую анимацию. Это нетрудно сделать, как вы можете видеть выше, мы просто добавили атрибут name к компоненту перехода: name="fade".

Теперь, когда у нас есть наши хуки, мы можем создать переход, используя их:

1
2
3
4
5
6
7
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.25s ease-out;
}
 
.fade-enter, .fade-leave-to {
  opacity: 0;
}

Классы .fade-enter-active и .fade-leave-active будут использоваться, когда мы применяем фактический переход. Это обычный CSS, вы можете перенести это в cubic-beziers для упрощения, изменения задержки или указания других свойства для перехода. На самом деле, это так же хорошо работало бы, при замене класса перехода на сами классы компонента по умолчанию. Они не обязательно должны определяться хуками переходных компонентов. Они просто побудут там и подождут, пока это свойство изменится и использует их для перехода, если это произойдет. (Так что вам все равно понадобится компонент перехода и .fade-enter, .fade-leave-to). Одна из причин, по которой я использую это в enter-active и leave-active, заключается в том, что я могу повторно использовать один и тот же переход для других элементов, а не запускать код, применяя один и тот же CSS по умолчанию к каждому экземпляру.

Еще одно замечание: я использую ease-out для обоих активных классов. Это неплохо работает, например, для изменения прозрачности. Но вы можете обнаружить, что если вы переходите на такие свойства, как трансформация, то вы можете отделить их и использовать ease-out для класса enter-active, а ease-in для класса enter-leave (или cubic-beziers, которые неясно следуют той же кривой). Мне даже кажется, что анимация выглядит более… красивой 😀

Так же мы также установили .fade-enter и .fade-to в opacity: 0. Это будут первая и последняя позиции анимации, начальное состояние при монтировании и конечное состояние при размонтировании. Вы можете подумать, что вам нужно установить opacity: 1 на .fade-enter-to и .fade-leave, но это необязательно, так как это состояние по умолчанию для компонента, поэтому оно будет излишним. Переходы и анимация CSS всегда будут использовать состояние по умолчанию, если не указано иное.

See the Pen Демо переходов без bk классов by RinaMina (@FurkMe) on CodePen.

Отлично работает! Но что произойдет, если мы хотим, чтобы то что позади исчезло из вида: окошко заняло центр внимания, а бэкграунд потерял фокус? Мы не сможем использовать компонент , поскольку этот компонент работает на основе монтирования и размонтирования, и бэкграунд просто торчит. Мы можем сделать переходные классы на основе состояния и использовать классы для создания переходов CSS, которые изменяют фон:

1
2
3
4
5
6
<div>
<p>Let's trigger this here modal!</p>
<button> Hide child Show child </button>
 
</div>
<pre lang="html5" line="1">
1
2
3
4
5
6
7
8
.bk {
  transition: all 0.1s ease-out;
}
 
.blur {
  filter: blur(2px);
  opacity: 0.4;
}
1
2
3
4
5
6
7
8
9
10
11
new Vue({
  el: '#app',
  data() {
    return {
      isShowing: false,
      bkClass: 'bk',
      blurClass: 'blur'
    }
  },
  ...
});

See the Pen Демо переходов by RinaMina (@FurkMe) on CodePen.

CSS анимация

Теперь, когда мы понимаем, как работают переходы, мы можем использовать эти основные концепции, чтобы создать приятные CSS анимации. Мы по-прежнему будем использовать компонент transition, и мы по-прежнему будем давать ему имя, позволяя нам иметь одни и те же хуки класса. Разница здесь заключается в том, что вместо того, чтобы просто устанавливать конечное состояние и говорить как мы хотим, чтобы он интерполировал между началом и концом, мы будем использовать @keyframes в CSS для создания забавных и красивых эффектов.

В последнем разделе мы немного поговорили о том, как вы можете назначить специальное имя для компонента перехода, которое затем можно использовать в качестве класса. Но в этом разделе мы сделаем еще один шаг и применим разные хуки класса к различным разделам анимации. Вспомните, что enter-active и leave-active — это то, где происходит всё сочное оживление! Мы можем установить разные свойства для каждого из этих хуков класса, но мы можем пойти еще на один шаг и дать каждому классу специальные классы (больше классов богу классов! >:D):

enter-active-class="toasty"
leave-active-class="bounceOut"

Это означает, что мы можем повторно использовать эти классы или даже подключаться к классам из библиотек CSS анимации.

Предположим, мы хотим, чтобы мяч подпрыгнул и укатился:

1
2
3
4
5
6
7
8
<div id="app">
 
<h3>Bounce the Ball!</h3>
<button> Get it gone! Here we go! </button>
<div></div>
&nbsp;
 
</div>

Для отскока нам понадобится много ключевых кадров, если мы хотим сделать это в CSS (хотя в JS это может быть одна строка кода), мы также будем использовать SASS миксы для хранения наших стилей DRY (лучше не повторяйте это сами). Мы также назначили класс .ballmove-enter, чтобы компонент знал, что он должен начинаться за экраном:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@mixin ballb($yaxis: 0) {
  transform: translate3d(0, $yaxis, 0);
}
 
@keyframes bouncein { 
  1% { @include ballb(-400px); }
  20%, 40%, 60%, 80%, 95%, 99%, 100% { @include ballb() }
  30% { @include ballb(-80px); }
  50% { @include ballb(-40px); }
  70% { @include ballb(-30px); }
  90% { @include ballb(-15px); }
  97% { @include ballb(-10px); }
}
 
.bouncein { 
  animation: bouncein 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
 
.ballmove-enter {
  @include ballb(-400px);
}

Чтобы вытащить мяч, нам необходимо использовать две разные анимации. Это происходит потому, что трансформация применяется ко всему дочернему компоненту, и вращение всего этого приведет к слишком частому rotation. Поэтому мы переместим компонент по экрану с translation и бдуем вращаем шар внутри с помощью rotation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@keyframes rollout { 
  0% { transform: translate3d(0, 300px, 0); }
  100% { transform: translate3d(1000px, 300px, 0); }
}
 
@keyframes ballroll {
  0% { transform: rotate(0); }
  100% { transform: rotate(1000deg); }
}
 
.rollout { 
  width: 60px;
  height: 60px;
  animation: rollout 2s cubic-bezier(0.55, 0.085, 0.68, 0.53) both; 
  div {
    animation: ballroll 2s cubic-bezier(0.55, 0.085, 0.68, 0.53) both; 
  }
}

See the Pen Ball Bouncing using Vue transition and CSS Animation by RinaMina (@FurkMe) on CodePen.

Чудесные Transition mode

Помните, что я говорила, что в Vue transitions есть приятные мелочи, которые делают задротов, вроде меня, счастливее? Вот мелочь, которую я реально обожаю. Если вы попытаетесь перевести один компонент во время окончания другого компонента, вы встретите странную штуку, когда оба компонента будут существовать одновременно, а затем снова встанут на свои места (этот небольшой пример из документов Vue):

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

In-out: текущий элемент ждет, пока новый элемент не перейдет в действие.

Out-in: сначала переходит текущий элемент, а затем новый.

Ознакомьтесь с демо ниже. Вы можете увидеть mode- out-in на компонент перехода так, чтобы он отображал только одну часть:

 <transition name="flip" mode="out-in">
  <slot v-if="!isShowing"></slot>
  <img v-else src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/cartoonvideo14.jpeg" />
</transition>

See the Pen Vue in-out modes by RinaMina (@FurkMe) on CodePen.


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

See the Pen Vue in-out modes by RinaMina (@FurkMe) on CodePen.

JS Анимации

У нас есть несколько классных JS-хуков, которые очень просты в использовании или не использовании, в зависимости от необходимости для нашей анимации. Все хуки переходят в параметр el (short для элемента), за исключением фактических хуков анимации (enter и leave), которые также передаются в качестве параметра done, используемого чтобы сообщить Vue, что анимация завершена. Заметьте, что мы также привязываем CSS к значению false, чтобы компонент знал, что мы будем использовать JavaScript вместо CSS.

1
2
3
4
5
6
7
8
9
10
11
12
13
<transition 
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"
 
  @before-Leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
  :css="false">
 
 </transition>

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

1
2
3
4
5
6
7
8
9
10
methods: {
   enterEl(el, done) {
     //entrance animation
     done();
  },
  leaveEl(el, done) {
    //exit animation
    done();
  },
}

Вот пример использования, чтобы подключиться к временной шкале GreenSock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
new Vue({
  el: '#app',
  data() {
    return {
      message: 'This is a good place to type things.',
      load: false
    }
  },
  methods: {
    beforeEnter(el) {
      TweenMax.set(el, {
        transformPerspective: 600,
        perspective: 300,
        transformStyle: "preserve-3d",
        autoAlpha: 1
      });
    },
    enter(el, done) {
      ...
 
      tl.add("drop");
      for (var i = 0; i < wordCount; i++) {
        tl.from(split.words[i], 1.5, {
          z: Math.floor(Math.random() * (1 + 150 - -150) + -150),
          ease: Bounce.easeOut
        }, "drop+=0." + (i/ 0.5));
       ...
 
    }
  }
});

See the Pen Vue Book Content Typer by RinaMina (@FurkMe) on CodePen.0

Две самые интересные штуки на которые стоит обратить внимание:

  • Я передаю {onComplete: done} как параметр экземпляра Timeline.
  • И я использую beforeEnter хук для размещения TweenMax.set, что позволит мне задавать любые свойства для слов, нужных для анимации, прежде чем она произойдет в случае transform-style: preserve-3d.

Важно отметить, что вы также можете настроить то, что хотите для анимации непосредственно в CSS, как состояние по умолчанию. Иногда люди спрашивают, как решить, что установить в CSS и что установить в TweenMax.set. Как правило, я обычно добавляю любые свойства, которые мне нужны для анимации в TweenMax.set. Таким образом, если что-то в анимации меняется, и мне нужно его обновить, это уже часть моего рабочего процесса.

Анимации хуков жизненного цикла

Все это неплохо, но что произойдет, если вам нужно оживить что-то очень сложное, что работает с тоннами элементов DOM? Это лучший момент для использования методов жизненного цикла. В следующей анимации мы использовали как компонент transition, так и метод mount() для создания некоторых анимаций.

See the Pen NjrJMm by FurFurFur (@FurFurFur) on CodePen.

Когда мы переходим к одному элементу, мы будем использовать компонент перехода, например, когда появляется обводка у кнопки телефона:

1
2
3
4
5
6
7
<transition 
  @before-enter="beforeEnterStroke"
  @enter="enterStroke"
  :css="false"
  appear>
  <path class="main-button" d="M413,272.2c5.1,1.4,7.2,4.7,4.7,7.4s-8.7,3.8-13.8,2.5-7.2-4.7-4.7-7.4S407.9,270.9,413,272.2Z" transform="translate(0 58)" fill="none"/>
</transition>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
beforeEnterStroke(el) {
  el.style.strokeWidth = 0;
  el.style.stroke = 'orange';
},
enterStroke(el, done) {
  const tl = new TimelineMax({
    onComplete: done
  });
 
  tl.to(el, 0.75, {
    strokeWidth: 1,
    ease: Circ.easeOut
  }, 1);
 
  tl.to(el, 4, {
    strokeWidth: 0,
    opacity: 0,
    ease: Sine.easeOut
  });
},

Но когда компонент появляется в начале и у нас есть 30 элементов или более анимаций, было бы не круто обертывать каждый из них в отдельный компонент transition. Таким образом, мы будем использовать методы жизненного цикла, упомянутые в разделе 3 этой серии серии статей, для привязки к тому же событию, которое использует переходный хук под хуком: mounted()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Tornadoarea = {
  template: '#tornadoarea',
  mounted () {
    let audio = new Audio('https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/tornado.mp3'),
        tl = new TimelineMax();
 
    audio.play();
    tl.add("tornado");
 
    //tornado timeline begins
    tl.staggerFromTo(".tornado-group ellipse", 1, {
      opacity: 0
    }, {
      opacity: 1,
      ease: Sine.easeOut
    }, 0.15, "tornado");
    ...
    }
};

Всё это используется в зависимости от эффективности того или иного способа и, как вы видите, позволяет создавать действительно сложные эффекты. Vue предлагает красивый и гибкий API, не только для создания сложной архитектуры front-end, но также для гибких и цельных переходов между views.

Заключение

Эта серия статей не относится к документации. Несмотря на то, что мы много изучили, вам предстоит еще больше: маршрутизация, микшины, рендеринг на стороне сервера и т.д. Еще есть очень много удивительных вещей. Отправляйтесь к отличным докам и этому репозиторию, полному примеров и ресурсов, которые вам нужно изучать дальше. Существует также хорошая книга под названием «The Majesty of Vue.js».

Я надеюсь, что в этой серии статей достаточно хорошо рассказывается, почему меня так впечатляет Vue и это сподвигнет вас к работе с этим чудесным фреймворком!

Серия статей

  1. Рендеринг, директивы и события
  2. Components, Props и Slots
  3. Vue-cli
  4. Vuex
  5. Анимации (Вы здесь)
Оригинальная статья: https://css-tricks.com/intro-to-vue-5-animations/

1 комментарий

  1. @Дамир пишет:

    Огонь! Большое спасибо!)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: