I am using Vue.js transitions to fade elements in/out based on conditional rendering.
First, I am transitioning a group of components. This is working perfectly!
<div>
<transition-group name="component-fade" mode="out-in">
<component-one key="1" v-show="foo === 'one'" :type="type"/>
<component-one key="2" v-show="foo === 'two'" :type="type"/>
<component-one key="3" v-show="foo === 'three'" :type="type"/>
</transition-group>
</div>
Each component <component-one.../> is identical. I am rendering a bunch of <div> elements:
// component-one.vue
<template>
<div>
<div id="div-one">
<transition name="fade" mode="out-in">
<div key="one" v-if="foo === 'bar'">
<h3>My First Div</h3>
...
</div>
<div key="two" v-if="foo === 'bazz'">
<h3>My Second Div</h3>
...
</div>
<div key="three" v-if="foo === 'other'">
<h3>My Third Div</h3>
...
</div>
...
</transition>
</div>
</div>
</template>
The functionally works great. The components fade in/out nicely as to the div elements. However, I am getting a warning from vue:
[Vue warn]: can only be used on a single element. Use for lists.
This makes sense as I am transitioning a group of div elements. However, if I use <transition-group> the mode of out-in is no longer working. As I toggle through my div elements, they snap in/out of position as they are fading in/out. I have tried every combination -- including using v-if or v-show to see if that made a difference.
How can I use the transitions I have (that work) but not generate the warning?
`Adding "keys" on each item.
try this.
new Vue({
el: "#app",
data: {
foo: "bar"
},
methods: {
}
})
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="foo = 'bar'">show bar</button>
<button #click="foo = 'bazz'">show bazz</button>
<button #click="foo = 'other'">show other</button>
<transition name="fade" mode="out-in">
<div key="one" v-if="foo === 'bar'">
<h3>bar</h3>
</div>
<div key="two" v-if="foo === 'bazz'">
<h3>bazz</h3>
</div>
<div key="three" v-if="foo === 'other'">
<h3>other</h3>
</div>
</transition>
</div>
I believe that the transition element is supposed to wrap just one element and that the reason v-show isn't working is because v-show doesn't actually add/remove anything from the DOM, it just toggles the visibility attribute. If you use any logic that could theoretically show more than one at the same time, you'll get a warning. So what I think you'd really want is for your transition element to wrap each of the individual component-one elements. v-show should take care of the individual element's visibility. Basically, you need the final markup to look like this:
<transition name="fade" mode="out-in">
<div key="one" v-if="foo === 'bar'">
<h3>My First Div</h3>
...`enter code here`
</div>
</transition>
<transition name="fade" mode="out-in">
<div key="two" v-if="foo === 'bar'">
<h3>My Second Div</h3>
...
</div>
</transition>
Try doing either:
A v-for on the transition component that loops through a list of all the items you'd like to render. Each transition should have its own component-one child as it loops through the list.
<transition v-for="(item, index) in items" name="fade" mode="out-in">
<component-one :key="index" v-show="foo === item" :type="type"/>
</transition>
Wrapping the contents of component-one inside a transition. That would include the transition inside the component-one element. Then you can render component-one elements without any transition elements as a parent.
component-one
<transition name="fade" mode="out-in">
<div>
<!-- Component One contents... -->
</div>
</transition>
main
<div>
<component-one key="1" v-show="foo === 'one'" :type="type"/>
<component-one key="2" v-show="foo === 'two'" :type="type"/>
<component-one key="3" v-show="foo === 'three'" :type="type"/>
</div>
The above answers are in fact correct and should be considered by anyone who comes across this thread. I also wanted to add what I found was the underlying issue.
I was using <transition-group> on my parent element(s) that I was trying to transition between. Each component that I was transitioning to had multiple div's underneath. This is what was bubbling up (for lack of better words) that was looking for a <transition-group>.
TL;DR:
The error was coming from the child component(s) that needed to make use of <transition-group>.
Seems obvious now, but hope can save other folks some time.
Related
I've done the research but can't find a good answer. My parent and child component code is below. How do I pass the index for the v-for loop in the parent to the child component for use there? I want to hide any of the gauges past #4 for mobile screens, but show all of them on a desktop.
Parent:
<div class="col-md-8 col-xs-6">
<div class="row flex-nowrap">
<data-block
v-for="(gauge, index) in device.gauges"
:metric="gauge.metric"
:value="gauge.value"
:unit="gauge.unit"
:alarm="gauge.alarm"
:idx="index">
</data-block>
</div>
</div>
Child:
app.component('data-block', {
props: ['alarm', 'metric','value','unit','idx'],
template: `<div v-bind:class="'col card px-0 text-center border' + ((alarm) ? ' border-danger':' border-success') + ((idx > 3) ? ' d-none d-md-block':'')">\
<div class="card-header p-1">{{metric}}</div>\
<div class="card-body p-1 align-middle">\
<h1 class=" text-center display-1 font-weight-normal text-nowrap">{{value}}</h1>\
</div>\
<div class="card-footer p-1">{{unit}}</div>\
</div>`,
created: ()=> console.log(this.idx) //yields "undefined"
})
You're passing the idx prop correctly, but Instead of checking its value inside created hook, try displaying it in the template instead, to make sure it's not an issue with timing (it might not be defined when the child component is created):
<div>{{idx}}</div>
Also, to make the code easier to read and write, I would suggest you to move the static classes to class attribute and the dynamic classes to v-bind:class attribute, and also make it multiline:
template: `
<div
class="col card px-0 text-center border"
:class="{
'd-none d-md-block': idx > 3,
'border-danger': alarm,
'border-success': !alarm
}"
>
...
`
I want to animate a block with posts that can be filtered.
Some filters trigger a computed method filteredPosts and they are assigned to a component liek that <block-article :posts="filteredPosts" />
In my <block-article> component I have something like that :
<template>
<div class="posts">
<div v-for="post in posts" :key="post.id"></div>
</div>
</template>
I want div .posts animate like a translation bottom/fade out on disappear and translation top/ fade in on appear.
I tried that :
<template>
<transition name="slide-fade">
<div class="posts">
<div v-for="post in posts" :key="post.id"></div>
</div>
</transition>
</template>
with corresponding css classes but it doesn't work.
I tried that :
<template>
<div class="posts">
<transition-group name="slide-fade">
<div v-for="post in posts" :key="post.id"></div>
</transition-group>
</div>
</template>
but my class .posts is a grid and here I lost the grid behavior.
THE AIM is to animate the entire div .posts rather than each elements of the v-for.
Any idea ?
Thanks all,
I finally achieve this with :
<transition name="slide-fade">
<div :key="posts.length" class="posts"></div>
</transition>
Nothe the :key="posts.length"
The problem is when posts.length doesn't change but it works in a lots of case. I will search how to fix this exception.
If you animate entire div, you should use transition, but in this case all inner elements not animated. If you want to animate all inner elements. You should use transition-group
In your case I think, need use all this method with build-in tag attribute.
Becouse in dock you can read
https://v2.vuejs.org/v2/guide/transitions.html
Unlike transition, it renders an actual element: a span by default. You can change the element that’s rendered with the tag attribute.
So you can write like this(its not full code, you must add name, key and other attrs)
<transition>
<transition-group tag="div" class="posts">
<div v-for="post in posts"></div>
</transition-group>
</transition>
My understanding of transitions in vue.js is that you use <transition>
to animate between individual elements and <transition-group> to animate a whole list.
It seems as though if you wanted to animate a transition within a list item you'd use <transition> within the list. e.g. something like this:
<span v-for="item in items">
<transition>
<div>
Transition from this...
</div>
<div>
...to this.
</div>
</transition>
</span>
Yet, when I make that change the animation doesn't work. Is this an expected behavior?
Update: after some tinkering, I have found that my original hypothesis was correct. I was just doing something else wrong. But it's worth noting for anyone else who comes across this problem.
You can use <transition> inside a list if you want to animate individual components of the list.
You use transition groups to transition all children in the same way.
In addition, try setting the transition group before your v-for
new Vue({
el: "#app",
data: {
items : [
{message: 'sometext', id: 1},
{message: 'sometext', id: 2},
{message: 'sometext', id: 3}
],
id : 3
},
methods: {
addItem(){
this.id++
this.items.push({message: 'sometext', id: this.id});
},
enter(){
console.log('transition enter called');
}
}
})
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="container">
<button #click="addItem()">Add Item</button>
<transition-group :name="'fade'" v-on:enter="enter">
<span v-for="item in items" v-bind:key="item.id">
{{item.message}}
</span>
</transition-group>
</div>
</div>
I am not exactly sure what you are trying to do with your example.
If you would like to transition a list
<transition-group name="fade" tag="span">
<div v-for="item in items" v-bind:key="item">
{{ item }}
</div>
</transition-group>
If you would like to transition between two items.
<transition name="fade">
<div v-show="whatever === true">
Transition from this...
</div>
</transition>
<transition name="fade">
<div v-show="whatever === false">
...to this.
</div>
</transition>
Could someone explain me, why the following code doesn't work?
<template>
<div class="modal">
<transition name="slide-in">
<div #click.stop class="modal__container">
<div #click="close" class="modal__button">
<span class="modal__button--close">Close</span>
</div>
</div>
</transition>
<transition name="fade-in">
<div #click="close" class="modal__overlay"/>
</transition>
</div>
</template>
I'm trying to create modal with two different animations (slide-in for text area and fade-in for modal overlay).
If i delete the element with class modal and edit code to the following everything works fine.
<template>
<transition name="slide-in">
<div #click.stop class="modal__container">
<div #click="close" class="modal__button">
<span class="modal__button--close">Close</span>
</div>
</div>
</transition>
</template>
Referencing Vue.js documentation on transitions
Vue provides a variety of ways to apply transition effects when items are inserted, updated, or removed from the DOM
That means that DOM nodes that transition applies classes to, should be those which are inserted/updated/removed.
Since it is a modal window, I assume that it has v-if directive applied in parent component to handle it's visibility. In order for transition to work, it should wrap DOM element that will be updated.
You can understand it more easily if you move code of your modal window into the parent component. Just for better visualization of elements tree and transition's behavior.
In first example, conditional rendering (v-if) applies to <div class="modal">, which is not wrapped with transition - therefore no animation will be triggered. At the same time, nested nodes are wrapped with transition, but there is nothing that will update or remove them. They are statically displayed and inserted initially on component's creation. Nothing to animate.
In order for it to work as expected following structure would be advisable:
<template>
<transition name="fade-in">
<div
class="modal__overlay"
#click="close"
>
<transition name="slide-in">
<div
v-if="containerVisible"
class="modal__container"
#click.stop
>
<div #click="close" class="modal__button">
<span class="modal__button--close">Close</span>
</div>
</div>
</transition>
</div>
</transition>
</template>
This solution expects modal__overlay to have position: fixed; style and variable containerVisible to be set to true inside mounted hook of modal component.
I have 1 page with 2 components. Component A has a button, when clicking disalbe A and show component B.
If i use transition i am getting this error.
I want a nice fadeout A and fade in B, how can i achieve?
Error:
[Vue warn]: <transition> can only be used on a single element. Use <transition-group> for lists.
App.js
<transition name="fade">
<div class="fade-enter-active" v-show="datatable">
<component-a :title="'AAA'"></component-a>
<button v-on:click="showCompB">Show B and disable A</button>
</div>
<div class="fade-enter-active" v-show="componentb">
<component-b :title="'BBB'"></component-b>
</div>
</transition>
export default {
data() {
return {
datatable: true,
componentb: false,
etc etc
Good morning sir,
As the error stated, the <transition> component can be used only with a single child element. You can learn more about that here: https://v2.vuejs.org/v2/guide/transitions.html
You could instead use two <transition> components to handle the fade animation for each one of your elements like so:
<transition name="fade">
<div v-show="datatable">
<component-a :title="'AAA'"></component-a>
<button v-on:click="showCompB">Show B and disable A</button>
</div>
</transition>
<transition name="fade">
<div v-show="componentb">
<component-b :title="'BBB'"></component-b>
</div>
</transition>
The fade animation will be applied to each div whenever componentb and datatable is visible or not.
Hope that helps you.