简体   繁体   中英

Vue.js transition on changing selected element from a list

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM