Vue.js transition on changing selected element from a list - javascript

I have a list of Profiles that open an "edit profile" screen. This screen slided in from the left. When I select a profile, if there is a screen already selected, I want it to slide out first, change the selected profile data and then slide in.
What happens now is: when I first select one element, the screen slides in. When I change the selected element, screen stays and don't slide out and back in.
Here is a gif to show how it's behaving now:
My code is:
Vue Method:
editProfile: function (index){
// this.editingProfile = false;
this.setProfile(index);
this.editingProfile = true;
}
Html View:
<transition name="fade" mode="out-in">
<div v-if="editingProfile" id="edit-profile">
<input placeholder="Profile Name" v-model="synced.profiles[synced.selectedProfile].name">
</div>
</transition>
CSS:
.fade-enter-active, .fade-leave-active {
transition: all .2s;
/* transform:translateX(0); */
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform:translateX(-100%);
}
How do I make it properly slide out and then back in when changing a profile?

I think I was incorrect with my comment. One way you can do this is to leverage :key and a v-if so that you can tell Vue to render a panel if you have selected one and then transition between panels that way. You won't need to have a transition-group then.
The thing is :key is what tells Vue that everything has changed. If you leave it off, Vue tries to recycle as much as it can. See the docs: Transitioning Between Elements
When toggling between elements that have the same tag name, you
must tell Vue that they are distinct elements by giving them unique
key attributes. Otherwise, Vue’s compiler will only replace the
content of the element for efficiency. Even when technically
unnecessary though, it’s considered good practice to always key
multiple items within a <transition> component.
Consider the minimal example below:
const panels = [{
title: "Hello"
},
{
title: "World"
},
{
title: "Foo"
},
{
title: "Bar"
}
];
const app = new Vue({
el: "#app",
data() {
return {
panels,
activePanel: null
};
},
computed: {
titles() {
return this.panels.map(panel => panel.title);
}
},
methods: {
handleTitleClick(idx) {
if (this.activePanel === idx) {
this.activePanel = null;
return;
}
this.activePanel = idx;
}
}
});
body {
margin: 0;
padding: 0;
}
#app {
display: flex;
align-items: stretch;
height: 100vh;
}
#panel-set {
flex: 1 0 70%;
display: flex;
}
#side-panel {
flex: 1 0 30%;
}
.panel {
padding: 1em;
flex: 1 0;
background-color: rgba(0, 0, 0, 0.2);
}
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: transform 500ms ease-in-out;
}
.slide-fade-enter,
.slide-fade-leave-to {
transform: translateX(-100%);
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<div id="panel-set">
<transition name="slide-fade" mode="out-in">
<div class="panel" v-if="activePanel !== null" :key="activePanel">
<h2>{{panels[activePanel].title}}</h2>
<p>Lorem ipsum</p>
</div>
</transition>
</div>
<div id="side-panel">
<ul>
<li v-for="(title, idx) in titles" #click="handleTitleClick(idx)">{{title}}</li>
</ul>
</div>
</div>

The root cause is v-if="editingProfile" always true after showing one profile in your codes.
One solution is set it to false first, then in this.$nextTick to set it to true again. But you have to put this.editingProfile = true inside one setTimeout and delay time = transition time. Otherwise, slide out effect will be overwritten.
Like below demo:
new Vue({
el: '#emit-example-simple',
data() {
return {
editingProfile: false,
synced : {
profiles: [{'name':'A'}, {'name':'B'}, {'name':'C'}],
selectedProfile: 0
},
}
},
methods: {
editProfile: function (index){
this.editingProfile = !this.editingProfile
this.$nextTick(() => {
setTimeout(()=> {
this.synced.selectedProfile = index
this.editingProfile = true
}, 1200)
})
}
}
})
.fade-enter-active, .fade-leave-active {
transition: all 1.2s;
/* transform:translateX(0); */
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform:translateX(-100%);
border: 1px solid white;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="emit-example-simple">
<button #click="editProfile(0)">Profile 1</button>
<button #click="editProfile(1)">Profile 2</button>
<button #click="editProfile(2)">Profile 3</button>
<transition name="fade" mode="out-in">
<div v-if="editingProfile" id="edit-profile">
<input style="border: 5px solid red;" placeholder="Profile Name" v-model="synced.profiles[synced.selectedProfile].name">
</div>
</transition>
</div>
Or you can consider to use Group transition like below simple demo:
new Vue({
el: '#emit-example-simple',
data() {
return {
editingProfile: false,
profileContainers: [true, false],
synced : {
profiles: [{'name':'A'}, {'name':'B'}, {'name':'C'}],
selectedProfile: 0
},
}
},
methods: {
editProfile: function (index){
this.synced.selectedProfile = index
this.profileContainers = this.profileContainers.map((x)=>!x)
}
}
})
.list-items-enter-active {
transition: all 1.2s;
}
.list-items-leave-active {
transition: all 1.2s;
}
.list-items-enter, .list-items-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform:translateX(-100%);
border: 1px solid white;
}
.list-item {
display: inline-block;
border: 6px solid red;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="emit-example-simple">
<button #click="editProfile(0)">Profile 1</button>
<button #click="editProfile(1)">Profile 2</button>
<button #click="editProfile(2)">Profile 3</button>
<transition-group name="list-items" tag="p">
<div v-for="(item, index) in profileContainers" :key="index" v-if="item">
<input style="border: 5px solid red;" placeholder="Profile Name" v-model="synced.profiles[synced.selectedProfile].name">
</div>
</transition-group>
</div>

Related

How to get the class from a button with embedded divs VUE 3

I'm converting elements over from jQuery into Vue 3 JS.
I'm creating a component called "CardID" that will have person's initials.
In some cases it will be selectable, in others not. So, I'm passing that class when I'm using the component to determine if it is selectable or not.
If a user clicks on it and it has the class "is-selectable", a blue border will go around it. Clicks again, and it goes away.
In Vue3, I believe the only way I can get the className on the element is through using the event.target.
This works great if I click the grey area of the button. But if I click on the white circle or the letter, it won't work.
Any help will be appreciated. I know the same thing happens in vanilla JavaScript.
Using the component...
<CardID class="is-selectable" symbolText="K" />
The code:
<template>
<button class="card" :class="{active: isActive}" #click="checkClass">
<div class="card__circle">
<div class="card__symbol">{{ symbolText }}</div>
</div>
</button>
</template>
<script>
export default {
name: "CardID",
data: function () {
return {
isActive: false
}
},
props: {
symbolText: String,
},
methods: {
checkClass(evt) {
if(evt.target.className.includes("is-selectable")) {
this.isActive = !this.isActive;
}
}
}
}
</script>
<style scoped>
.active {
border: 2px solid #1971D4;
box-shadow: inset 0px 0px 0px 4px #fff;
}
.card {
position: relative;
width: 160px;
height: 115px;
border-radius: 8px;
border: 2px solid transparent;
background-color: #E7E7E8;
}
.card__circle {
position: absolute;
top: 50%;
left: 50%;
z-index: 10;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.7);
transform: translate(-50%, -50%);
}
.card__symbol {
position: absolute;
top: 50%;
left: 50%;
z-index: 20;
font-size: 2.25rem;
color: #1d242b;
transform: translate(-50%, -50%);
}
Custom components can have custom props, so you should create a selectable prop in you CardID component and use that to check behaviour.
Something like:
<template>
<button class="card" :class="{active: isActive}" #click="checkClass">
<div class="card__circle">
<div class="card__symbol">{{ symbolText }}</div>
</div>
</button>
</template>
<script>
export default {
name: "CardID",
data: function () {
return {
isActive: false
}
},
props: {
selectable: Boolean,
symbolText: String,
},
methods: {
checkClass(evt) {
if(this.selectable) {
this.isActive = !this.isActive;
}
}
}
}
</script>
And use your component like:
// non selectable card
<CardID :selectable="false" symbolText="K" />
// selectable card
<CardID :selectable="true" symbolText="K" />
PS: also note that you can set a default value for your props, which is usually useful for boolean properties
Try to use this.$el.className instead of evt.target.className :
<template>
<button class="card" :class="{active: isActive}" #click="checkClass">
<div class="card__circle">
<div class="card__symbol">{{ symbolText }}</div>
</div>
</button>
</template>
<script>
export default {
name: "CardID",
data: function () {
return {
isActive: false
}
},
props: {
symbolText: String,
},
methods: {
checkClass(evt) {
if(this.$el.className.includes("is-selectable")) {
this.isActive = !this.isActive;
}
}
}
}
</script>

Vue.js second radio button is selected on app load

I have the following Quiz written using Vue.js where everything works like a charm...
except one thing, which I can't find a solution,
is it my code causing this bug? or I need approach it differently?
Update: now that I have added unique names for each element, as one of the answers suggest, The second menu item is always selected by default on page load... any hint on how to overcome this issue?
<script>
// Create a quiz object with a title and two questions.
// A question has one or more answer, and one or more is valid.
var quiz = {
"title":"Quizorama",
"questions":[
{
"text":"Lalala",
"audio":"TextTo-1-1.mp3",
"responses":[
{
"text":"Incorrect"
},
{
"text":"Incorrect"
},
{
"text":"Correct",
"correct":true
}
]
},
{
"text":"Something",
"audio":"57633709.mp3",
"responses":[
{
"text":"Correct",
"correct":true
},
{
"text":"Incorrect"
},
{
"text":"Incorrect"
}
]
},
{
"text":"Question",
"audio":"57633709.mp3",
"responses":[
{
"text":"Correct",
"correct":true
},
{
"text":"Incorrect"
},
{
"text":"Incorrect"
}
]
}
]
};
</script>
<div class="wrapper" id="page-wrapper">
<div class="centered-content " id="content" tabindex="-1">
<div class="row">
<main class="site-main" id="main">
<div id="app">
<h1>{{ quiz.title }}</h1>
<!-- index is used to check with current question index -->
<div v-for="(question, index) in quiz.questions">
<!-- Hide all questions, show only the one with index === to current question index -->
<transition name="slide-fade">
<div v-show="index === questionIndex">
<h2>{{ question.text }}</h2>
<audio width="450" controls :src="question.audio"></audio>
<ul>
<li v-for="response in question.responses"
v-bind:correctOrNot="response.correct"
v-bind:class="{ active: isActive }">
<label>
  <input type="radio"
v-bind:value="checkResponse(response.correct)"
v-bind:name="nameMethod(index ,response.text,
questionIndex)"
v-model="userResponses[index]"
> {{response.text}}
</label>
</li>
</ul>
<!-- The two navigation buttons -->
<!-- Note: prev is hidden on first question -->
<!-- <button v-if="questionIndex > 0" v-on:click="prev">
otra oportunidad?
</button> -->
<button v-on:click="next">
Next pleeeeease!
</button>
</div>
</transition>
</div>
<transition name="slide-fade">
<div v-show="questionIndex === quiz.questions.length">
<h3>
yer results are da following bro:
</h3>
<p class="puntaje">
{{ score() }}
</p>
</div>
</transition>
</div>
<script>
new Vue({
el: '#app',
data: {
quiz: quiz,
// Store current question index
questionIndex: 0,
// An array initialized with "false" values for each question
// It means: "did the user answered correctly to the question n?" "no".
userResponses: Array(quiz.questions.length).fill(false),
isActive: false,
},
// The view will trigger these methods on click
methods: {
checkResponse: function(response){
let checkResponseValue = response;
if (response == true){
return true;
} else {
return false;
}
},
nameMethod: function(index, responseText, questionIndex){
var index = index;
var questionIndexValue = questionIndex
var responseText = responseText;
var name = index + responseText+'_'+ questionIndexValue;
return name;
},
next: function() {
console.log(this);
this.isActive = true;
setTimeout(() => {
// move to next question
this.questionIndex++;
this.isActive = false;
}, 3000);
},
updateMessage: function () {
this.message = 'updated';
},
// Go to previous question
prev: function() {
this.questionIndex--;
},
editModal: function(id){
console.log(id);
},
// Return "true" count in userResponses
score: function() {
let scorePercent = Math.round(this.userResponses.filter(function(val) { return val }).length * 100 / this.questionIndex);
let newResult;
if(scorePercent == 0 ){
newResult = "you suck , not even one good response mate ";
return newResult
}
  if(scorePercent < 30){
newResult = scorePercent + "% Was Good, so you need to practice more mate";
return newResult
}
if(scorePercent < 70){
newResult = scorePercent + "% yar a ducking star but there is more to improve";
return newResult
}
if(scorePercent == 100){
newResult = scorePercent + "% you are a godlike creature made flesh";
return newResult
}
}
}
});
</script>
<style>
p.puntaje {
font-size: 25px;
color: #333333;
margin-bottom: 40px;
background: #fce373;
padding: 13px;
border-radius: 100px;
text-align: center;
}
main#main {
margin: auto;
}
#app h1 {
font-size: 66px;
font-weight: 900;
color: #b1b1b1;
}
#app h2 {
font-size: 125px;
color: #a282bb;
}
#app ul {
list-style: none;
padding: 0;
}
/* Enter and leave animations can use different */
/* durations and timing functions. */
.slide-fade-enter-active {
transition: all 0.5s ease;
transition-delay: 2s;
}
.slide-fade-leave-active {
transition: all 0.5s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
transition: all 0.5s ease;
}
#app button {
background: #00BCD4;
width: 200px;
margin-bottom: 30px;
border-radius: 100px;
font-size: 17px !important;
padding: 7px 17px;
border: none;
}
#app li label {
font-size: 25px;
background: #fff2b6;
border-radius: 100px;
padding-left: 17px;
padding-right: 17px;
margin-top: 16px;
padding-top: 5px;
transition: all 0.5s ease;
}
#app li label:hover{
background: #fce372;
cursor:pointer;
transition: all 0.5s ease;
}
li.active label {
background: #ee734c !important; transition: all 0.5s ease;
}
li[correctOrNot="true"].active label {
background: #9ad18b !important; transition: all 0.5s ease;
}
.slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(50px);
opacity: 0;
transition: all 0.5s ease;
}
</style>
This is a simple HTML problem. You define the radio buttons with the response.correct value, which is undefined for the incorrect options and also identical for both (no matter if it is null or false).
Your output might be like this:
<input type="radio" name="index">Incorrect
<input type="radio" name="index">Incorrect
<input type="radio" name="index" value="true">Correct
If you have a group of radio buttons and several have the same value (or none), these are basical the same input. Try to define a unique value for each radio button with the same name.

Why is position absolute needed for move transition when removing item from list in Vue.js

https://v2.vuejs.org/v2/guide/transitions.html#List-Move-Transitions gives an example in which when an item is removed from a list, the other items smoothly move in its place. For this to work the element is styled with:
.list-complete-leave-active {
position: absolute;
}
I wonder why it doesn't work without this?
new Vue({
el: '#list-complete-demo',
data: {
items: [1,2,3,4,5,6,7,8,9]
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
}
}
})
.list-complete-item {
transition: all 1s;
display: inline-block;
margin-right: 10px;
}
.list-complete-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div id="list-complete-demo" class="demo">
<button v-on:click="remove">Remove</button>
<transition-group name="list-complete" tag="p">
<span
v-for="item in items"
v-bind:key="item"
class="list-complete-item"
>
{{ item }}
</span>
</transition-group>
</div>
In this case, the position of the actively removed number is set from static to absolute, so that the element does not take up any space. Why is this important? The animation slides the rest of the numbers to the left, and if the removed item takes up space, this does not happen. You could substitute this for, for example, position: fixed or margin-right: -8px. All of these will animate the bounding box from about 18px (8px from the number, 10 from the margin) to 0px, neatly sliding the rest of the inline positioned items to the left.
new Vue({
el: '#list-complete-demo',
data: {
items: [1,2,3,4,5,6,7,8,9]
},
methods: {
randomIndex: function () {
return Math.floor(0)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
}
}
})
.list-complete-item {
transition: all 10s;
display: inline-block;
margin-right: 10px;
}
.list-complete-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div id="list-complete-demo" class="demo">
<button v-on:click="remove">Remove</button>
<transition-group name="list-complete" tag="p">
<span
v-for="item in items"
v-bind:key="item"
class="list-complete-item"
>
{{ item }}
</span>
</transition-group>
</div>

How to animate list items in Vue when one is removed

i have a vertical list of items, each of which can be removed. I put my items inside a transition-group and created simple opacity and transform transitions for them. The transitions on the removed elements work as expected, however if I remove an element which is not placed at the bottom, the ones beneath just jump up and take its place without any transition. I Can't find a way to target this behaviour.
All I want is just that the elements below slide up smoothly.
Is there any way to achieve this effect by using css transitipms and Vue's animation hooks?
Here is a demo: https://jsfiddle.net/gcp18nq0/
Template:
<div id="app">
<div class="form">
<label for="name">Name</label>
<input type="text" id="name" v-model="name">
<button #click="addPlayer">Add player</button>
</div>
<div class="players">
<transition-group name="player">
<div class="panel" v-for="player in players" :key="player.id">
<h2>
{{ player.name}}
<span class="remove" #click="removePlayer(player.id)">Remove</span>
</h2>
</div>
</transition-group>
</div>
</div>
Script:
data() {
return {
name: "",
players: [
{id: 1, name: 'Player1'},
{id: 2, name: 'Player2'},
{id: 3, name: 'Player3'},
]
}
},
methods: {
addPlayer: function () {
//,,,,
},
removePlayer: function (playerId) {
//...
}
}
});
CSS
.form {
margin:0 auto;
width:400px;
}
.panel {
width: 400px;
margin: 10px auto;
overflow: hidden;
border: 1px solid;
text-align: center;
}
.remove {
float: right;
cursor: pointer;
text-decoration: underline;
font-size: 12px;
vertical-align: bottom
}
.player-enter,
.player-leave-to
/* .fade-leave-active below version 2.1.8 */
{
opacity: 0;
}
.player-enter {
transform: translateY(30%);
}
.player-leave-to {
transform: translateX(30%);
}
.player-enter-active,
.player-leave-active {
transition: all 1.5s;
}
.player-move {
transition: all 1.5s;
}
The only working way I found was by adding position:absolute on "player-leave-active" state but since the element collapses it changes its vertical position, which is not the desired effect. I also tried changing the height but there the elements below still jump up a bit after the height is set to 0.
Im sure that this can be achieved easily with jQuery but i believe that there should be a way to do it without js.
Thank you in advance!
p.s. its my first post here, so i hope that it was explained clearly enough.
So I made some small tweaks to your fiddle: https://jsfiddle.net/gcp18nq0/1/ and hopefully that is what you looking for.
The most important change was setting display: inline-block on the .panel class, according to the Vue documentation:
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.
new Vue({
el: "#app",
data() {
return {
name: "",
players: [{
id: 1,
name: 'Batman'
},
{
id: 2,
name: 'Robin'
},
{
id: 3,
name: 'Superman'
},
{
id: 4,
name: 'Spiderman'
},
]
}
},
methods: {
addPlayer: function() {
const newPlayer = {
id: this.players.length + 1,
name: this.name,
};
this.players.push(newPlayer);
},
deletePlayer: function(playerId) {
let playerToRemove = this.players.find((player) => {
return player.id === playerId;
});
let playerIndex = this.players.indexOf(playerToRemove);
this.players.splice(playerIndex, 1);
}
}
});
.form {
margin: 0 auto;
width: 400px;
}
.panel {
width: 400px;
margin: 6px auto;
overflow: hidden;
border: 1px solid;
text-align: center;
transition: all 1s;
display: inline-block;
}
.players {
position: relative;
text-align: center;
}
.remove {
float: right;
cursor: pointer;
text-decoration: underline;
font-size: 12px;
vertical-align: bottom
}
.player-enter,
.player-leave-to {
opacity: 0;
}
.player-enter {
transform: translateY(30%);
}
.player-leave-to {
transform: translateX(300%);
}
.player-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div class="form">
<label for="name">Name</label>
<input type="text" id="name" v-model="name">
<button #click="addPlayer">Add player</button>
</div>
<div class="players">
<transition-group name="player" tag="div">
<div class="panel" v-for="player in players" :key="player.id">
<h2>
{{ player.name}}
<span class="remove" #click="deletePlayer(player.id)">Remove</span>
</h2>
</div>
</transition-group>
</div>
</div>

How to animate the sorting of a list with Vue.js

I’m trying to animate the sorting of a list with Vue.js, but not all items are animated. Do you know why? And how to make it work?
new Vue({
el: '#app',
data: {
reverse: 1,
items: [
{ name: 'Foo' },
{ name: 'Bar' },
{ name: 'Baz' },
{ name: 'Qux' }
]
}
})
.moving-item {
transition: all 1s ease;
-webkit-transition: all 1s ease;
}
ul {
list-style-type: none;
padding: 0;
position: relative;
}
li {
position: absolute;
border: 1px solid #42b983;
height: 20px;
width: 150px;
padding: 5px;
margin-bottom: 5px;
color: #42b983;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.0-alpha.2/vue.min.js"></script>
<div id="app">
<button on-click="reverse = Math.abs(reverse-1)">
<span v-if="reverse == 0">△</span>
<span v-if="reverse == 1">▽</span> Order
</button>
<ul>
<li class="moving-item" v-for="item in items | orderBy 'name' reverse" bind-style="{ top: ($index * 35) + 'px'}">{{ item.name }}</li>
</ul>
</div>
I believe the problem is that only one of the elements is remaining in the DOM during the sort. The other three are being removed and reinserted to satisfy the new ordering – but as a result they are not triggering an animation.
Typically, animation is done using the Vue transition system (http://vuejs.org/guide/transitions.html). However, the same basic problem of deletion and reinsertion not tracking position state will occur using that technique. Usually, items are animated independent of their previous and new positions (like fade-out in their old position and fade-in in their new one).
If you really need to animate from the the old position to the new one, I think you would need to write your own Javascript transition that remembers the previous position of each item before it is removed and animates it to the new position when it is inserted.
There is an example here which should be a good starting point: http://vuejs.org/guide/transitions.html#JavaScript_Only_Transitions
Another option is to not sort by a filter and do it in javascript instead (so that the v-for only renders once). Then target your bind-style against a new index parameter on your items like this:
new Vue({
el: '#app',
data: {
reverse: 1,
items: [
{ name: 'Foo', position: 0 },
{ name: 'Bar', position: 1 },
{ name: 'Baz', position: 2 },
{ name: 'Qux', position: 3 }
]
},
methods: {
changeOrder: function (event) {
var self = this;
self.reverse = self.reverse * -1
var newItems = self.items.slice().sort(function (a, b) {
var result;
if (a.name < b.name) {
result = 1
}
else if (a.name > b.name) {
result = -1
}
else {
result = 0
}
return result * self.reverse
})
newItems.forEach(function (item, index) {
item.position = index;
});
}
}
})
.moving-item {
transition: all 1s ease;
-webkit-transition: all 1s ease;
}
ul {
list-style-type: none;
padding: 0;
position: relative;
}
li {
position: absolute;
border: 1px solid #42b983;
height: 20px;
width: 150px;
padding: 5px;
margin-bottom: 5px;
color: #42b983;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.0-alpha.2/vue.min.js"></script>
<div id="app">
<button on-click="changeOrder">
<span v-if="reverse == -1">△</span>
<span v-if="reverse == 1">▽</span> Order
</button>
<ul>
<li class="moving-item" v-for="item in items" bind-style="{ top: (item.position * 35) + 'px'}">{{ item.name }}</li>
</ul>
</div>
V2 has this built in: https://v2.vuejs.org/v2/guide/transitions.html#Transition-Modes
Check out "List Move Transitions" and the example with the shuffle button.

Categories