diff --git a/src/.vuepress/config.js b/src/.vuepress/config.js index cfcf9aea7a..4fd39a1d51 100644 --- a/src/.vuepress/config.js +++ b/src/.vuepress/config.js @@ -31,6 +31,25 @@ const sidebar = { '/guide/component-edge-cases' ] }, + { + title: 'Transitions & Animation', + collapsable: false, + children: [ + '/guide/transitions-overview', + '/guide/transitions-enterleave', + '/guide/transitions-list', + '/guide/transitions-state' + ] + }, + { + title: 'Internals', + collapsable: false, + children: [ + '/guide/reactivity', + '/guide/optimizations', + '/guide/change-detection' + ] + }, { title: 'Reusability & Composition', collapsable: false, diff --git a/src/.vuepress/public/images/transition.png b/src/.vuepress/public/images/transition.png new file mode 100644 index 0000000000..f2cf80182b Binary files /dev/null and b/src/.vuepress/public/images/transition.png differ diff --git a/src/guide/transitions-enterleave.md b/src/guide/transitions-enterleave.md new file mode 100644 index 0000000000..65b34f22fd --- /dev/null +++ b/src/guide/transitions-enterleave.md @@ -0,0 +1,573 @@ +# Enter & Leave Transitions + +Vue provides a variety of ways to apply transition effects when items are inserted, updated, or removed from the DOM. This includes tools to: + +- automatically apply classes for CSS transitions and animations +- integrate 3rd-party CSS animation libraries, such as [Animate.css](https://animate.style/) +- use JavaScript to directly manipulate the DOM during transition hooks +- integrate 3rd-party JavaScript animation libraries, such as Velocity.js + +On this page, we'll only cover entering, leaving, and list transitions, but you can see the next section for [managing state transitions](transitions-state.html). + +## Transitioning Single Elements/Components + +Vue provides a `transition` wrapper component, allowing you to add entering/leaving transitions for any element or component in the following contexts: + +- Conditional rendering (using `v-if`) +- Conditional display (using `v-show`) +- Dynamic components +- Component root nodes + +This is what an example looks like in action: + +```html +
+ + + +

hello

+
+
+``` + +```js +const Demo = { + data() { + return { + show: true + } + } +} + +Vue.createApp(Demo).mount('#demo') +``` + +```css +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.5s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} +``` + +

+ See the Pen + Simple Transition Component by Vue (@Vue) + on CodePen. +

+ + +When an element wrapped in a `transition` component is inserted or removed, this is what happens: + +1. Vue will automatically sniff whether the target element has CSS transitions or animations applied. If it does, CSS transition classes will be added/removed at appropriate timings. + +2. If the transition component provided [JavaScript hooks](#javascript-hooks), these hooks will be called at appropriate timings. + +3. If no CSS transitions/animations are detected and no JavaScript hooks are provided, the DOM operations for insertion and/or removal will be executed immediately on next frame (Note: this is a browser animation frame, different from Vue's concept of `nextTick`). + +### Transition Classes + +There are six classes applied for enter/leave transitions. + +1. `v-enter-from`: Starting state for enter. Added before element is inserted, removed one frame after element is inserted. + +2. `v-enter-active`: Active state for enter. Applied during the entire entering phase. Added before element is inserted, removed when transition/animation finishes. This class can be used to define the duration, delay and easing curve for the entering transition. + +3. `v-enter-to`: **Only available in versions 2.1.8+.** Ending state for enter. Added one frame after element is inserted (at the same time `v-enter` is removed), removed when transition/animation finishes. + +4. `v-leave-from`: Starting state for leave. Added immediately when a leaving transition is triggered, removed after one frame. + +5. `v-leave-active`: Active state for leave. Applied during the entire leaving phase. Added immediately when leave transition is triggered, removed when the transition/animation finishes. This class can be used to define the duration, delay and easing curve for the leaving transition. + +6. `v-leave-to`: **Only available in versions 2.1.8+.** Ending state for leave. Added one frame after a leaving transition is triggered (at the same time `v-leave` is removed), removed when the transition/animation finishes. + +![Transition Diagram](/images/transition.png) +TODO: update diagram + +Each of these classes will be prefixed with the name of the transition. Here the `v-` prefix is the default when you use a `` element with no name. If you use `` for example, then the `v-enter-from` class would instead be `my-transition-enter-from`. + +`v-enter-active` and `v-leave-active` give you the ability to specify different easing curves for enter/leave transitions, which you'll see an example of in the following section. + +### CSS Transitions + +One of the most common transition types uses CSS transitions. Here's an example: + +```html +
+ + + +

hello

+
+
+``` + +```js +const Demo = { + data() { + return { + show: true + } + } +} + +Vue.createApp(Demo).mount('#demo') +``` + +```css +/* Enter and leave animations can use different */ +/* durations and timing functions. */ +.slide-fade-enter-active { + transition: all 0.3s ease-out; +} + +.slide-fade-leave-active { + transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1); +} + +.slide-fade-enter-from, +.slide-fade-leave-to { + transform: translateX(20px); + opacity: 0; +} +``` + +

+ See the Pen + Different Enter and Leave Transitions by Vue (@Vue) + on CodePen. +

+ + +### CSS Animations + +CSS animations are applied in the same way as CSS transitions, the difference being that `v-enter-from` is not removed immediately after the element is inserted, but on an `animationend` event. + +Here's an example, omitting prefixed CSS rules for the sake of brevity: + +```html +
+ + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis + enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi + tristique senectus et netus. +

+
+
+``` + +```js +const Demo = { + data() { + return { + show: true + } + } +} + +Vue.createApp(Demo).mount('#demo') +``` + +```css +.bounce-enter-active { + animation: bounce-in 0.5s; +} +.bounce-leave-active { + animation: bounce-in 0.5s reverse; +} +@keyframes bounce-in { + 0% { + transform: scale(0); + } + 50% { + transform: scale(1.5); + } + 100% { + transform: scale(1); + } +} +``` + +

+ See the Pen + CSS Animation Transition Example by Vue (@Vue) + on CodePen. +

+ + +### Custom Transition Classes + +You can also specify custom transition classes by providing the following attributes: + +- `enter-from-class` +- `enter-active-class` +- `enter-to-class` (2.1.8+) +- `leave-from-class` +- `leave-active-class` +- `leave-to-class` (2.1.8+) + +These will override the conventional class names. This is especially useful when you want to combine Vue's transition system with an existing CSS animation library, such as [Animate.css](https://daneden.github.io/animate.css/). + +Here's an example: + +```html + + +
+ + + +

hello

+
+
+``` + +```js +const Demo = { + data() { + return { + show: true + } + } +} + +Vue.createApp(Demo).mount('#demo') +``` + +### Using Transitions and Animations Together + +Vue needs to attach event listeners in order to know when a transition has ended. It can either be `transitionend` or `animationend`, depending on the type of CSS rules applied. If you are only using one or the other, Vue can automatically detect the correct type. + +However, in some cases you may want to have both on the same element, for example having a CSS animation triggered by Vue, along with a CSS transition effect on hover. In these cases, you will have to explicitly declare the type you want Vue to care about in a `type` attribute, with a value of either `animation` or `transition`. + +### Explicit Transition Durations + +TODO: validate and provide an example + +> New in 2.2.0+ + +In most cases, Vue can automatically figure out when the transition has finished. By default, Vue waits for the first `transitionend` or `animationend` event on the root transition element. However, this may not always be desired - for example, we may have a choreographed transition sequence where some nested inner elements have a delayed transition or a longer transition duration than the root transition element. + +In such cases you can specify an explicit transition duration (in milliseconds) using the `duration` prop on the `` component: + +```html +... +``` + +You can also specify separate values for enter and leave durations: + +```html +... +``` + +### JavaScript Hooks + +You can also define JavaScript hooks in attributes: + +```html + + + +``` + +```js +// ... +methods: { + // -------- + // ENTERING + // -------- + + beforeEnter(el) { + // ... + }, + // the done callback is optional when + // used in combination with CSS + enter(el, done) { + // ... + done() + }, + afterEnter(el) { + // ... + }, + enterCancelled(el) { + // ... + }, + + // -------- + // LEAVING + // -------- + + beforeLeave(el) { + // ... + }, + // the done callback is optional when + // used in combination with CSS + leave(el, done) { + // ... + done() + }, + afterLeave(el) { + // ... + }, + // leaveCancelled only available with v-show + leaveCancelled(el) { + // ... + } +} +``` + +These hooks can be used in combination with CSS transitions/animations or on their own. + +When using JavaScript-only transitions, **the `done` callbacks are required for the `enter` and `leave` hooks**. Otherwise, the hooks will be called synchronously and the transition will finish immediately. Adding `:css="false"` will also let know Vue to skip CSS detection. Aside from being slightly more performant, this also prevents CSS rules from accidentally interfering with the transition. + +Now let's dive into an example. Here's a JavaScript transition using [GreenSock](https://greensock.com/): + +```html + + +
+ + + +

+ Demo +

+
+
+``` + +```js +const Demo = { + data() { + return { + show: false + } + }, + methods: { + beforeEnter(el) { + gsap.set(el, { + scaleX: 0.8, + scaleY: 1.2 + }) + }, + enter(el, done) { + gsap.to(el, { + duration: 1, + scaleX: 1.5, + scaleY: 0.7, + opacity: 1, + x: 150, + ease: 'elastic.inOut(2.5, 1)', + onComplete: done + }) + }, + leave(el, done) { + gsap.to(el, { + duration: 0.7, + scaleX: 1, + scaleY: 1, + x: 300, + ease: 'elastic.inOut(2.5, 1)' + }) + gsap.to(el, { + duration: 0.2, + delay: 0.5, + opacity: 0, + onComplete: done + }) + } + } +} + +Vue.createApp(Demo).mount('#demo') +``` + +

+ See the Pen + JavaScript Hooks Transition by Vue (@Vue) + on CodePen. +

+ + +## Transitions on Initial Render + +If you also want to apply a transition on the initial render of a node, you can add the `appear` attribute: + +```html + + + +``` + +## Transitioning Between Elements + +We discuss [transitioning between components](#transitioning-between-components) later, but you can also transition between raw elements using `v-if`/`v-else`. One of the most common two-element transitions is between a list container and a message describing an empty list: + +```html + + + +
+

Sorry, no items found.

+
+``` + +It's actually possible to transition between any number of elements, either by using multiple `v-if`s or binding a single element to a dynamic property. For example: + +TODO: rewrite example and put in codepen example + +```html + + + + + +``` + +Which could also be written as: + +```html + + + +``` + +```js +// ... +computed: { + buttonMessage() { + switch (this.docState) { + case 'saved': return 'Edit' + case 'edited': return 'Save' + case 'editing': return 'Cancel' + } + } +} +``` + +### Transition Modes + +There's still one problem though. Try clicking the button below: + +TODO: redo the example + +As it's transitioning between the "on" button and the "off" button, both buttons are rendered - one transitioning out while the other transitions in. This is the default behavior of `` - entering and leaving happens simultaneously. + +Sometimes this works great, like when transitioning items are absolutely positioned on top of each other: + +TODO: redo the example + +And then maybe also translated so that they look like slide transitions: + +TODO: redo the example + +Simultaneous entering and leaving transitions aren't always desirable though, so Vue offers some alternative **transition modes**: + +- `in-out`: New element transitions in first, then when complete, the current element transitions out. + +- `out-in`: Current element transitions out first, then when complete, the new element transitions in. + +Now let's update the transition for our on/off buttons with `out-in`: + +```html + + + +``` + +TODO: example + +With one attribute addition, we've fixed that original transition without having to add any special styling. + +The `in-out` mode isn't used as often, but can sometimes be useful for a slightly different transition effect. Let's try combining it with the slide-fade transition we worked on earlier: + +TODO: redo the example + +Pretty cool, right? + +## Transitioning Between Components + +Transitioning between components is even simpler - we don't even need the `key` attribute. Instead, we wrap a [dynamic component](components.html#Dynamic-Components): + +TODO: update to Vue 3 + +```html + + + +``` + +```js +new Vue({ + el: '#transition-components-demo', + data: { + view: 'v-a' + }, + components: { + 'v-a': { + template: '
Component A
' + }, + 'v-b': { + template: '
Component B
' + } + } +}) +``` + +```css +.component-fade-enter-active, +.component-fade-leave-active { + transition: opacity 0.3s ease; +} +.component-fade-enter, .component-fade-leave-to +/* .component-fade-leave-active below version 2.1.8 */ { + opacity: 0; +} +``` + +TODO: example diff --git a/src/guide/transitions-list.md b/src/guide/transitions-list.md new file mode 100644 index 0000000000..82fcfaf06a --- /dev/null +++ b/src/guide/transitions-list.md @@ -0,0 +1,445 @@ +# List Transitions + +So far, we've managed transitions for: + +- Individual nodes +- Multiple nodes where only 1 is rendered at a time + +So what about for when we have a whole list of items we want to render simultaneously, for example with `v-for`? In this case, we'll use the `` component. Before we dive into an example though, there are a few things that are important to know about this component: + +- Unlike ``, it renders an actual element: a `` by default. You can change the element that's rendered with the `tag` attribute. +- [Transition modes](/guide/transitions-enterleave#transition-modes) are not available, because we are no longer alternating between mutually exclusive elements. +- Elements inside are **always required** to have a unique `key` attribute. +- CSS transition classes will be applied to inner elements and not to the group/container itself. + +### List Entering/Leaving Transitions + +Now let's dive into an example, transitioning entering and leaving using the same CSS classes we've used previously: + +```html +
+ + + + + {{ item }} + + +
+``` + +```js +const Demo = { + data() { + return { + items: [1, 2, 3, 4, 5, 6, 7, 8, 9], + nextNum: 10 + } + }, + methods: { + randomIndex() { + return Math.floor(Math.random() * this.items.length) + }, + add() { + this.items.splice(this.randomIndex(), 0, this.nextNum++) + }, + remove() { + this.items.splice(this.randomIndex(), 1) + } + } +} + +Vue.createApp(Demo).mount('#list-demo') +``` + +```css +.list-item { + display: inline-block; + margin-right: 10px; +} +.list-enter-active, +.list-leave-active { + transition: all 1s ease; +} +.list-enter-from, +.list-leave-to { + opacity: 0; + transform: translateY(30px); +} +``` + +

+ See the Pen + Transition List by Vue (@Vue) + on CodePen. +

+ + +There's one problem with this example. When you add or remove an item, the ones around it instantly snap into their new place instead of smoothly transitioning. We'll fix that later. + +### List Move Transitions + +The `` component has another trick up its sleeve. It can not only animate entering and leaving, but also changes in position. The only new concept you need to know to use this feature is the addition of **the `v-move` class**, which is added when items are changing positions. Like the other classes, its prefix will match the value of a provided `name` attribute and you can also manually specify a class with the `move-class` attribute. + +This class is mostly useful for specifying the transition timing and easing curve, as you'll see below: + +```html + + +
+ + +
  • + {{ item }} +
  • +
    +
    +``` + +```js +const Demo = { + data() { + return { + items: [1, 2, 3, 4, 5, 6, 7, 8, 9] + } + }, + methods: { + shuffle() { + this.items = _.shuffle(this.items) + } + } +} + +Vue.createApp(Demo).mount('#flip-list-demo') +``` + +```css +.flip-list-move { + transition: transform 0.8s ease; +} +``` + +

    + See the Pen + Transition-group example by Vue (@Vue) + on CodePen. +

    + + +This might seem like magic, but under the hood, Vue is using an animation technique called [FLIP](https://aerotwist.com/blog/flip-your-animations/) to smoothly transition elements from their old position to their new position using transforms. + +We can combine this technique with our previous implementation to animate every possible change to our list! + +```html + + +
    + + + + + + {{ item }} + + +
    +``` + +```js +const Demo = { + data() { + return { + items: [1, 2, 3, 4, 5, 6, 7, 8, 9], + nextNum: 10 + } + }, + methods: { + randomIndex() { + return Math.floor(Math.random() * this.items.length) + }, + add() { + this.items.splice(this.randomIndex(), 0, this.nextNum++) + }, + remove() { + this.items.splice(this.randomIndex(), 1) + }, + shuffle() { + this.items = _.shuffle(this.items) + } + } +} + +Vue.createApp(Demo).mount('#list-complete-demo') +``` + +```css +.list-complete-item { + transition: all 0.8s ease; + display: inline-block; + margin-right: 10px; +} + +.list-complete-enter-from, +.list-complete-leave-to { + opacity: 0; + transform: translateY(30px); +} + +.list-complete-leave-active { + position: absolute; +} +``` + +

    + See the Pen + Transition-group example by Vue (@Vue) + on CodePen. +

    + + +::: tip +One important note is that these FLIP transitions do not work with elements set to `display: inline`. As an alternative, you can use `display: inline-block` or place elements in a flex context. +::: + +These FLIP animations are also not limited to a single axis. Items in a multidimensional grid can be [transitioned too](https://codesandbox.io/s/github/vuejs/vuejs.org/tree/master/src/v2/examples/vue-20-list-move-transitions): + +TODO: example + +### Staggering List Transitions + +By communicating with JavaScript transitions through data attributes, it's also possible to stagger transitions in a list: + +```html + + +
    + + +
  • + {{ item.msg }} +
  • +
    +
    +``` + +```js +const Demo = { + data() { + return { + query: '', + list: [ + { msg: 'Bruce Lee' }, + { msg: 'Jackie Chan' }, + { msg: 'Chuck Norris' }, + { msg: 'Jet Li' }, + { msg: 'Kung Fury' } + ] + } + }, + computed: { + computedList() { + var vm = this + return this.list.filter(item => { + return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1 + }) + } + }, + methods: { + beforeEnter(el) { + el.style.opacity = 0 + el.style.height = 0 + }, + enter(el, done) { + gsap.to(el, { + opacity: 1, + height: '1.6em', + delay: el.dataset.index * 0.15, + onComplete: done + }) + }, + leave(el, done) { + gsap.to(el, { + opacity: 0, + height: 0, + delay: el.dataset.index * 0.15, + onComplete: done + }) + } + } +} + +Vue.createApp(Demo).mount('#demo') +``` + +

    + See the Pen + Staggered Lists by Vue (@Vue) + on CodePen. +

    + + +## Reusable Transitions + +Transitions can be reused through Vue's component system. To create a reusable transition, all you have to do is place a `` or `` component at the root, then pass any children into the transition component. + +TODO: refactor to Vue 3 + +Here's an example using a template component: + +```js +Vue.component('my-special-transition', { + template: '\ + \ + \ + \ + ', + methods: { + beforeEnter(el) { + // ... + }, + afterEnter(el) { + // ... + } + } +}) +``` + +And [functional components](render-function.html#Functional-Components) are especially well-suited to this task: + +```js +Vue.component('my-special-transition', { + functional: true, + render: function(createElement, context) { + var data = { + props: { + name: 'very-special-transition', + mode: 'out-in' + }, + on: { + beforeEnter(el) { + // ... + }, + afterEnter(el) { + // ... + } + } + } + return createElement('transition', data, context.children) + } +}) +``` + +## Dynamic Transitions + +Yes, even transitions in Vue are data-driven! The most basic example of a dynamic transition binds the `name` attribute to a dynamic property. + +```html + + + +``` + +This can be useful when you've defined CSS transitions/animations using Vue's transition class conventions and want to switch between them. + +Really though, any transition attribute can be dynamically bound. And it's not only attributes. Since event hooks are methods, they have access to any data in the context. That means depending on the state of your component, your JavaScript transitions can behave differently. + +TODO: refactor to Vue 3 + +```html + + +
    + Fade In: + + Fade Out: + + +

    hello

    +
    + + +
    +``` + +```js +new Vue({ + el: '#dynamic-fade-demo', + data() { + return { + show: true, + fadeInDuration: 1000, + fadeOutDuration: 1000, + maxFadeDuration: 1500, + stop: true + } + }, + mounted() { + this.show = false + }, + methods: { + beforeEnter(el) { + el.style.opacity = 0 + }, + enter(el, done) { + var vm = this + Velocity( + el, + { opacity: 1 }, + { + duration: this.fadeInDuration, + complete: function() { + done() + if (!vm.stop) vm.show = false + } + } + ) + }, + leave(el, done) { + var vm = this + Velocity( + el, + { opacity: 0 }, + { + duration: this.fadeOutDuration, + complete: function() { + done() + vm.show = true + } + } + ) + } + } +}) +``` + +TODO: example + +Finally, the ultimate way of creating dynamic transitions is through components that accept props to change the nature of the transition(s) to be used. It may sound cheesy, but the only limit really is your imagination. diff --git a/src/guide/transitions-overview.md b/src/guide/transitions-overview.md new file mode 100644 index 0000000000..62b3b2de02 --- /dev/null +++ b/src/guide/transitions-overview.md @@ -0,0 +1,146 @@ +# Transition & Animations Overview + +Vue offers some abstractions that can help work with transitions and animations, particularly in response to something changing. Some of these abstractions include: + +- Hooks for components entering and leaving the DOM, in both CSS and JS, using the built-in `` component. +- Transition Modes so that you can orchestrate ordering during a transition. +- Hooks for when multiple elements are updating in position, with FLIP techniques applied under the hood to increase performance, using the `` component. +- Transitioning different states in an application, with `watchers`. + +We will cover all of these and more in the next three sections in this Guide. However, aside from these useful API offerings, it's worth mentioning that the class and style declarations we covered earlier can be used to apply animations and transitions as well, for more simple use cases. + +In this next section, we'll go over some web animation and transitions basics, and link off to some resources for further exploration. If you're already familiar with web animation and how those principles might work with some of Vue's directives, feel free to skip this next section. For anyone else looking to learn a little more about web animation basics before diving in, read on. + +## Class-based Animations & Transitions + +Though the `` component can be wonderful for components entering and leaving, you can also activate an animation without mounting a component, by adding a conditional class. + +```html +
    + Push this button to do something you shouldn't be doing:
    + +
    + + Oh no! +
    +
    +``` + +```css +.shake { + animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; + transform: translate3d(0, 0, 0); + backface-visibility: hidden; + perspective: 1000px; +} + +@keyframes shake { + 10%, + 90% { + transform: translate3d(-1px, 0, 0); + } + + 20%, + 80% { + transform: translate3d(2px, 0, 0); + } + + 30%, + 50%, + 70% { + transform: translate3d(-4px, 0, 0); + } + + 40%, + 60% { + transform: translate3d(4px, 0, 0); + } +} +``` + +```js +const Demo = { + data() { + return { + noActivated: false + } + } +} + +Vue.createApp(Demo).mount('#demo') +``` + +

    + See the Pen + Create animation with a class by Vue (@Vue) + on CodePen. +

    + + +# Transitions with Style Bindings + +Some transition affects can be applied by interpolating values, for instance by binding a style to an element while an interaction occurs. Take this example for instance: + +TODO: use example + +## Performance + +You may notice that the animations shown above are using things like `transforms`, and applying strange properties like `perspective`- why were they built that way instead of just using `margin` and `top` etc? + +There are ways that we can create buttery smooth animations on the web by being aware of performance. We want to hardware accelerate elements when we can, and use properties that don't trigger repaints. Let's go over some of how we can accomplish this. + +### Hardware Acceleration + +If you'd like to + +TODO: finish writing + +## Timing + +For simple UI transitions, meaning from just one state to another with no intermediary states, it's common to use timings between 0.1s and 0.4s, and most folks find that _0.25s_ tends to be a sweet spot. Can you use that timing for everything? No, not really. If you have something that needs to move a greater distance or has more steps or state changes, 0.25s is not going to work as well and you will have to be much more intentional, and the timing will need to be more unique. That doesn't mean you can't have nice defaults that you repeat within your application, though. + +You may also find that entrances look better with slightly more time than an exit. The user typically is being guided during the entrance, and is a little less patient upon exit because they want to go on their way. + +## Easing + +Easing is an important way to convey depth in an animation. One of the most common mistakes newcomers to animation have is to use `ease-in` for entrances, and `ease-out` for exits. You'll actually need the opposite. + +If we were to apply these states to a transition, it would look something like this: + +```css +.button { + background: #1b8f5a; + /* applied to the initial state, so this transition will be applied to the return state */ + transition: background 0.25s ease-in; +} + +.button:hover { + background: #3eaf7c; + /* applied to the hover state, so this trnaisition will be applied when a hover is triggered */ + transition: background 0.35s ease-out; +} +``` + +

    + See the Pen + Transition Ease Example by Vue (@Vue) + on CodePen. +

    + + +You can get a lot of unique effects and make your animation very stylish by adjusting your easing. CSS allows you to modify this by adjusting a cubic bezier property, [this playground](https://cubic-bezier.com/#.17,.67,.83,.67) by Lea Verou is very helpful for exploring this. + +Though you can achieve great effects for simple animation with the two handles the cubic-bezier ease offers, JavaScript allows multiple handles, and therefore, allows for much more variance. + +TODO: add graphic + +Take this bounce, for instance. In CSS we have to declare each keyframe, up and down. In JavaScript, we can express all of that movement within the ease, by declaring `bounce` in the GreenSock API (other JS libraries have other types of easing defaults). + +TODO: add example + +We'll be using [GreenSock (GSAP)](https://greensock.com/) in some of the examples in the sections following, they have a great [ease visualizer](https://greensock.com/ease-visualizer) that will help you build nicely crafted eases. + +## Further Reading + +- [Designing Interface Animation: Improving the User Experience Through Animation by Val Head](https://www.amazon.com/dp/B01J4NKSZA/) +- [Animation at Work by Rachel Nabors](https://abookapart.com/products/animation-at-work) diff --git a/src/guide/transitions-state.md b/src/guide/transitions-state.md new file mode 100644 index 0000000000..8525e39a2a --- /dev/null +++ b/src/guide/transitions-state.md @@ -0,0 +1,241 @@ +# State Transitions + +Vue's transition system offers many simple ways to animate entering, leaving, and lists, but what about animating your data itself? For example: + +- numbers and calculations +- colors displayed +- the positions of SVG nodes +- the sizes and other properties of elements + +All of these are either already stored as raw numbers or can be converted into numbers. Once we do that, we can animate these state changes using 3rd-party libraries to tween state, in combination with Vue's reactivity and component systems. + +## Animating State with Watchers + +Watchers allow us to animate changes of any numerical property into another property. That may sound complicated in the abstract, so let's dive into an example using [GreenSock](https://greensock.com/): + +```html + + +
    + +

    {{ animatedNumber }}

    +
    +``` + +```js +const Demo = { + data() { + return { + number: 0, + tweenedNumber: 0 + } + }, + computed: { + animatedNumber() { + return this.tweenedNumber.toFixed(0) + } + }, + watch: { + number(newValue) { + gsap.to(this.$data, { duration: 0.5, tweenedNumber: newValue }) + } + } +} + +Vue.createApp(Demo).mount('#animated-number-demo') +``` + +

    + See the Pen + Transitioning State 1 by Vue (@Vue) + on CodePen. +

    + + +When you update the number, the change is animated below the input. This makes for a nice demo, but what about something that isn't directly stored as a number, like any valid CSS color for example? Here's how we could accomplish this with [Tween.js](https://github.com/tweenjs/tween.js) and [Color.js](https://github.com/brehaut/color-js): + +```html + + + +
    + + +

    Preview:

    + +

    {{ tweenedCSSColor }}

    +
    +``` + +```js +var Color = net.brehaut.Color + +new Vue({ + el: '#example-7', + data: { + colorQuery: '', + color: { + red: 0, + green: 0, + blue: 0, + alpha: 1 + }, + tweenedColor: {} + }, + created: function() { + this.tweenedColor = Object.assign({}, this.color) + }, + watch: { + color: function() { + function animate() { + if (TWEEN.update()) { + requestAnimationFrame(animate) + } + } + + new TWEEN.Tween(this.tweenedColor).to(this.color, 750).start() + + animate() + } + }, + computed: { + tweenedCSSColor: function() { + return new Color({ + red: this.tweenedColor.red, + green: this.tweenedColor.green, + blue: this.tweenedColor.blue, + alpha: this.tweenedColor.alpha + }).toCSS() + } + }, + methods: { + updateColor: function() { + this.color = new Color(this.colorQuery).toRGB() + this.colorQuery = '' + } + } +}) +``` + +```css +.example-7-color-preview { + display: inline-block; + width: 50px; + height: 50px; +} +``` + +TODO: put in example + +## Dynamic State Transitions + +As with Vue's transition components, the data backing state transitions can be updated in real time, which is especially useful for prototyping! Even using a simple SVG polygon, you can achieve many effects that would be difficult to conceive of until you've played with the variables a little. + +TODO: put in example + +See [this example](https://codesandbox.io/s/github/vuejs/vuejs.org/tree/master/src/v2/examples/vue-20-dynamic-state-transitions) for the complete code behind the above demo. + +## Organizing Transitions into Components + +Managing many state transitions can quickly increase the complexity of a Vue instance or component. Fortunately, many animations can be extracted out into dedicated child components. Let's do this with the animated integer from our earlier example: + +```html + + +
    + + + = {{ result }} +

    + + + = + +

    +
    +``` + +```js +// This complex tweening logic can now be reused between +// any integers we may wish to animate in our application. +// Components also offer a clean interface for configuring +// more dynamic transitions and complex transition +// strategies. +Vue.component('animated-integer', { + template: '{{ tweeningValue }}', + props: { + value: { + type: Number, + required: true + } + }, + data() { + return { + tweeningValue: 0 + } + }, + watch: { + value(newValue, oldValue) { + this.tween(oldValue, newValue) + } + }, + mounted() { + this.tween(0, this.value) + }, + methods: { + tween(startValue, endValue) { + var vm = this + function animate() { + if (TWEEN.update()) { + requestAnimationFrame(animate) + } + } + + new TWEEN.Tween({ tweeningValue: startValue }) + .to({ tweeningValue: endValue }, 500) + .onUpdate(function() { + vm.tweeningValue = this.tweeningValue.toFixed(0) + }) + .start() + + animate() + } + } +}) + +// All complexity has now been removed from the main Vue instance! +new Vue({ + el: '#example-8', + data() { + return { + firstNumber: 20, + secondNumber: 40 + } + }, + computed: { + result() { + return this.firstNumber + this.secondNumber + } + } +}) +``` + +TODO: put in example + +Within child components, we can use any combination of transition strategies that have been covered on this page, along with those offered by Vue's [built-in transition system](transitions.html). Together, there are very few limits to what can be accomplished. + +## Bringing Designs to Life + +To animate, by one definition, means to bring to life. Unfortunately, when designers create icons, logos, and mascots, they're usually delivered as images or static SVGs. So although GitHub's octocat, Twitter's bird, and many other logos resemble living creatures, they don't really seem alive. + +Vue can help. Since SVGs are just data, we only need examples of what these creatures look like when excited, thinking, or alarmed. Then Vue can help transition between these states, making your welcome pages, loading indicators, and notifications more emotionally compelling. + +Sarah Drasner demonstrates this in the demo below, using a combination of timed and interactivity-driven state changes: + +

    See the Pen Vue-controlled Wall-E by Sarah Drasner (@sdras) on CodePen.

    +