skip to Main Content

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?

2

Answers


  1. Chosen as BEST ANSWER

    Here is my current solution to prevent the transition on tab change:

    const { createApp, reactive, ref, nextTick } = Vue;
    const app = createApp({
      setup() {      
    
        const currentTabIdx = ref(0);
        
        const preventTransition = ref(true);
    
        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 changeTab (index) {
          preventTransition.value = true;
          currentTabIdx.value = index;
          nextTick(() => {
            preventTransition.value = false;
          });
         };
        
         function shuffleList() {
          listItems.value[currentTabIdx.value] = listItems.value[currentTabIdx.value].sort(() => Math.random() - 0.5);
        }
        
        return {
          currentTabIdx,
          preventTransition,
          listItems,
          changeTab,
          shuffleList,
        }
      }
    });
    
    app.component('list-component', {
      props: ['items', 'preventTransition'],
    
      template: `
        <ol>
          <transition-group :name="preventTransition ? 'show' : 'hide'">
            <li
              v-for="(effect, index) in items"
              :key="effect"
              class="move"
            >
              <div class="header">
                <h5>{{ effect.name }}</h5>
              </div>
            </li>
          </transition-group>
        </ol>
      `,
    });
    
    app.mount("#app");
    li.move {
      transition: 0.5s;
    }
    
    .hide-leave-active {
      display: none;
    }
    <script src="https://unpkg.com/vue@next"></script>
    <div id="app">
    
      <button @click="changeTab(0)">Tab 1</button> <!-- SWITCH TABS TO SEE SIDE EFFECTS -->
      <button @click="changeTab(1)">Tab 2</button> <!-- SWITCH TABS TO SEE SIDE EFFECTS -->
      | <button @click="shuffleList">Shuffle</button>
    
      <list-component :items="listItems[currentTabIdx]" :preventTransition="preventTransition"></list-component>
      
      
    </div>

    Reproduce

    1. parent: create a boolean to switch between named transitions
    2. parent: set to !boolean on tab change
    3. child: use :name
    4. child: hide previous list via display: none

    Now we can keep transitions in the child without side effects on tab change.


  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search