Search code examples
cssvue.jsvuejs3css-transitions

How to prevent transition on tab change?


I'm struggling to prevent triggering a transition after switching a tab.

Example

const { createApp, reactive, ref } = Vue;
const app = createApp({
  setup() {      
    // switch tabs to see the side effects
    const currentTabIdx = ref(0);

    const listItems = ref([
      [
        { name: 'John', id: 1 },
        { name: 'Joao', id: 2 },
        { name: 'Jean', id: 3 },
        { name: 'Gerard', id: 4 },
      ],
      [
        { name: 'Max', id: 1 },
        { name: 'Moritz', id: 2 },
        { name: 'Foo', id: 3 },
        { name: 'Mo', id: 4 },
      ],
    ])
    
     function shuffleList() {
      listItems.value[currentTabIdx.value] = listItems.value[currentTabIdx.value].sort(() => Math.random() - 0.5);
    }
    
    return {
      currentTabIdx,
      listItems,
      shuffleList
    }
  }
});

app.component('list-component', {
  props: ['items'],

  template: `
    <ol>
      <transition-group>
        <li
          v-for="(effect, index) in items"
          :key="effect"
        >
          <div class="header">
            <h5>{{ effect.name }}</h5>
          </div>
        </li>
      </transition-group>
    </ol>
  `,
});

app.mount("#app");
li {
  transition: 0.5s;
}

.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s;
}

.v-leave-active {
  position: absolute;
}

.v-enter,
.v-leave-to {
  opacity: 0;
}
<script src="https://unpkg.com/vue@next"></script>
<div id="app">

  <button @click="currentTabIdx=0">Tab 1</button> <!-- SWITCH TABS TO SEE SIDE EFFECTS -->
  <button @click="currentTabIdx=1">Tab 2</button> <!-- SWITCH TABS TO SEE SIDE EFFECTS -->
  | <button @click="shuffleList">Shuffle</button>

  <list-component :items="listItems[currentTabIdx]"></list-component>
  
  
</div>

Problem

The child component with the list and transition-group triggers side effects on tab navigation.

Tried

I have tried to disable the transition on the body with * { transition: none !important; } but than the list duplicates for a second.

Do you have an idea how to prevent the transition group on tab switch but enable it right afterwards?


Solution

  • How to prevent transition on tab change?

    Maybe, there's a best way than preventing it.

    If you can prefere this, you need to wrap the list-component with the keep-alive so that that the component is not destroyed and recreated each time the tab changes. As a result, the transition effect is maintained when reordering the list.

    const { createApp, reactive, ref, nextTick } = Vue;
    const app = createApp({
     setup() { 
     const currentTabIdx = ref(0);
    
     const listItems = ref([
     [
       { name: 'John', id: 1 },
       { name: 'Joao', id: 2 },
       { name: 'Jean', id: 3 },
       { name: 'Gerard', id: 4 },
     ],
     [
       { name: 'Max', id: 1 },
       { name: 'Moritz', id: 2 },
       { name: 'Foo', id: 3 },
       { name: 'Mo', id: 4 },
     ],
     ])
    
     function shuffleList() {
     listItems.value[currentTabIdx.value] = listItems.value[currentTabIdx.value].sort(() => Math.random() - 0.5);
     }
    
     nextTick(() => {
     shuffleList();
     });
    
     return {
     currentTabIdx,
     listItems,
     shuffleList
     }
     }
    });
    
    app.component('list-component', {
     props: ['items'],
    
     template: `
     <ol>
     <transition-group name="list" tag="ol" mode="out-in">
       <li
         v-for="(effect, index) in items"
         :key="effect.id"
       >
         <div class="header">
           <h5>{{ effect.name }}</h5>
         </div>
       </li>
     </transition-group>
     </ol>
     `,
    });
    
    app.mount("#app");
    li {
     transition: 0.5s;
    }
    
    .list-enter-active,
    .list-leave-active {
     transition: opacity 0.5s;
    }
    
    .list-leave-active {
     position: absolute;
    }
    
    .list-enter,
    .list-leave-to {
     opacity: 0;
    }
    <script src="https://unpkg.com/vue@next"></script>
    
    <div id="app">
     <button @click="currentTabIdx=0">Tab 1</button>
     <button @click="currentTabIdx=1">Tab 2</button>
     <button @click="shuffleList">Shuffle</button>
    
     <keep-alive>
     <component :is="'list-component'" :items="listItems[currentTabIdx]"></component>
     </keep-alive>
    </div>

    I use also the nextTick here to delay the call to shuffleList until the next DOM update cycle so that the transition-group applies the transition when the list is rendered.

    Edit

    the transition should stop between tabs, but within the list the transition should be applied.

    const { createApp, reactive, ref } = Vue;
    const app = createApp({
     setup() {     
       const currentTabIdx = ref(0);
       const listItems = ref([
         [
           { name: 'John', id: 1 },
           { name: 'Joao', id: 2 },
           { name: 'Jean', id: 3 },
           { name: 'Gerard', id: 4 },
         ],
         [
           { name: 'Max', id: 1 },
           { name: 'Moritz', id: 2 },
           { name: 'Foo', id: 3 },
           { name: 'Mo', id: 4 },
         ],
       ])
       
       function shuffleList() {
         listItems.value[currentTabIdx.value] = listItems.value[currentTabIdx.value].sort(() => Math.random() - 0.5);
       }
       
       return {
         currentTabIdx,
         listItems,
         shuffleList
       }
     }
    });
    
    app.component('list-component', {
     props: ['items'],
     template: `
       <ol>
         <transition-group>
           <li
             v-for="(effect, index) in items"
             :key="effect"
           >
             <div class="header">
               <h5>{{ effect.name }}</h5>
             </div>
           </li>
         </transition-group>
       </ol>
     `,
    });
    
    app.mount("#app");
    li {
     transition: 0.5s;
    }
    
    .v-enter-active,
    .v-leave-active {
     transition: opacity 0.5s;
    }
    
    .v-leave-active {
     position: absolute;
    }
    
    .v-enter,
    .v-leave-to {
     opacity: 0;
    }
    <script src="https://unpkg.com/vue@next"></script>
    
    <div id="app">
     <button @click="currentTabIdx=0">Tab 1</button> <!-- SWITCH TABS TO SEE SIDE EFFECTS -->
     <button @click="currentTabIdx=1">Tab 2</button> <!-- SWITCH TABS TO SEE SIDE EFFECTS -->
     | <button @click="shuffleList">Shuffle</button>
    
     <list-component v-show="currentTabIdx === 0" :items="listItems[0]"></list-component>
     <list-component v-show="currentTabIdx === 1" :items="listItems[1]"></list-component>
    </div>

    Here, the v-show is used to control the visibility of the list-component. When the currentTabIdx changes, only the display of the list component is toggled, and no transition is triggered.