Vue transition-group stutter

by Ting-Yuan Chang

HTML

<script src="https://unpkg.com/vue"></script>


<div id="app">
  <div>
    This is an example of a transition stutter that occurs when another part of the component updates mid-transition. Transition should appear normal with test1, test2, or test3. You should see a "stutter" of the transition-group when you use test4, test5, or test6.
  </div>
  <div>
    The stutter happens because, approximately 100ms after the transition, another value changes in the component which causes a different part of the DOM to re-render.
  </div>
  <br>
  <br>
  <br>
  <transition-group name="transition-group-opacity" tag="div" class="d-flex justify-content-center">
    <div v-for="(nav, index) in topLevelShowing" :key="nav" class="d-flex">
      <button type="button" class="btn" :class="selectedTag === nav ? 'transform-scale font-weight-bold text-uppercase' : null" @click="toggleNav(nav)" v-text="nav" />
    </div>
  </transition-group>
  <div v-if="selectedTag" class="text-center w-100">
    <div v-for="result in results" :key="result">
      <div v-text="result" />
      <div>
        Some thing <span>Other</span>
      </div>
    </div>
  </div>
</div>

CSS

.d-flex {
  display: flex;
}

.justify-content-center {
  justify-content: center;
}

.btn {
  padding: 10px;
  border: solid 1px transparent;
}

.text-center {
  text-align: center;
}

.transition-group-opacity-enter-active,
.transition-group-opacity-leave-active,
.transition-group-opacity-move {
  transition: 000ms linear;
  transition-property: opacity, transform;
}

.transition-group-opacity-enter {
  opacity: 0;
}

.transition-group-opacity-enter-to {
  opacity: 1;
}

.transition-group-opacity-leave-active {
  position: absolute;
}

.transition-group-opacity-leave-to {
  opacity: 0;
}

JavaScript

new Vue({
  el: '#app',
  data() {
    return {
      selectedTag: null,
      topLevel: ['test1', 'test2', 'test3', 'test4', 'test5', 'test6'],
      results: []
    };
  },
  computed: {
    topLevelShowing() {
      return this.topLevel.filter(item => {
        if (this.selectedTag) {
          if (this.selectedTag !== item) {
            return false;
          }
        }
        return true;
      });
    }
  },
  methods: {
    toggleNav(nav) {
      if (this.selectedTag) {
        this.selectedTag = null;
      } else {
        if (['test4', 'test5', 'test6'].includes(nav)) {
          this.fakeAjax();
        }
        this.selectedTag = nav;
      }
    },
    fakeAjax() {
      this.results = [];
      setTimeout(() => {
        this.results = ['test1', 'test2'];
      }, 500);
    }
  }
})