Search code examples
vue.jsnuxt.jsgsap

Flip.from() triggers onLeave() while <div> is still present


Overview

Goal

Use GSAP Flip to animate the div._selected when clicking another item.

Problem

Clicking on an item selects it properly, but doesn’t animate. The reason is because it thinks that div._selected has left the DOM. It’s there but in a new position.

As an aside, the animation works correctly when the List.vue data changes.


Context

Places.vue

<template>
  <list>
    <list-item
      class="place"
      v-for="place in places"
      :key="place.id"
      @click="selectPlace(place)"
      :selected="place.id === $store.state.places.selected_record_id">
      
      <div class="name">{{ place.name }}</div>

    </list-item>
  </list>
</template>

<script>
export default {
  name: 'places-page',

  methods: {
    selectRecord ({ id }) {
      this.selected_record = id;
    }
  }
}
</script>

ListItem.vue

<template>
  <li class="_item" @click="$emit('click')">
    
    <slot></slot>

    <!-- This is what moves around -->
    <div class="_selected"
      data-flip-id="selected"
      v-if="selected">
    </div>
  </li>
</template>

<script>
export default {
  name: 'list-item',

  props: {
    selected: { type: Boolean, default: false }
  }
}
</script>

List.vue

<template>
  <ul class="list-control">
    <slot></slot>
  </ul>
</template>

<script>
export default {
  name: 'list-control',

  data () {
    return {
      state: null
    };
  },

  beforeUpdate () {
    this.$data.state = Flip.getState(gsap.utils.toArray('.list-control [data-flip-id]'));
  },

  updated () {
    Flip.from(this.$data.state, {
      duration: 0.8,
      ease: 'expo.out',
      simple: true,
      nested: true,
      onEnter: elements => {
        console.log('*** onEnter', elements);
      },
      // When clicking on an item, this thinks that `div._selected`
      // has left the DOM and didn’t come back. Seems like
      // a race condition, but unsure how to proceed since the DOM
      // should be fully `updated` at this point
      onLeave: elements => {
        console.log('*** onLeave', elements);
      }
    });
  }
}
</script>

Solution

  • Need to add targets: gsap.utils.toArray('.list-control [data-flip-id]'). So:

    Flip.from(this.$data.state, {
      // ...
      targets: gsap.utils.toArray('.list-control [data-flip-id]'),
      // ...
    });