Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@inamori

Vueで親コンポーネントを跨いだTransitionを実現する

More than 1 year has passed since last update.

想定する使い道

たとえばVueでカードゲームを実装する時、

  • 山札置き場
  • 手札
  • カード

とCompnentを定義するとします。

そして今「カード」は「山札置き場」の子で、「山札置き場」から「手札」に移動するとします。
この時カードは位置アニメーションして欲しいわけですが、vueのtransitiontransition groupでは実現できません。
なぜなら親Componentが山札から手札へ変わるから1です。

デモ

具体的にはこういうことです。
四分割された地が親Component、数字が書かれた四角が子Componentで、クリックしたら別の親Componentに移動します。
こちらで実際に試せます

vue-parent-change-transition.gif

実装

Vueのtransition-groupでも使われているFLIPというアイデアを使っています。
基本は
- before-leaveで現在の位置を覚えて
- after-enter現在の位置 - 直前の位置だけtranslateし、その差分だけ移動アニメーション
という感じです。

parent-change-transition.vue
<template>
  <transition v-on:before-enter="beforeEnter" v-on:after-enter="afterEnter" v-on:before-leave="beforeLeave">
    <slot/>
  </transition>
</template>

<script>
//...

  export default {
    name: "parent-change-transition",
    methods: {
      beforeLeave (el) {
        previousPosition[this.childId] = el.getBoundingClientRect()
      },
      beforeEnter (el) {
        el.hidden = true
      },
      afterEnter (el) {
        el.hidden = false
        if (!previousPosition[this.childId]) return
        // 現在位置と直前位置の差だけtranslateし、tweenさせる
      }
    }
  }
</script>

しかしこれだけだとアニメーションするのは対象の子だけです。
つまり対象の子が移動した後、残された他の子が一瞬で移動してしまう為、何が起きたの分かりにくくなってしまいます。

最初はこの部分にだけリストトランジションを使えば良いのでは?と思いましたが、親に<transition-group>を使うと子の<transition>が無視される為上手くいきません。

そこで全ての子の位置を覚えておいて、前フレームと位置が変わったらアニメーションさせるという泥臭い実装が必要になります。

parent-change-transition.vue
<template>
  <transition v-on:before-enter="beforeEnter" v-on:after-enter="afterEnter" v-on:before-leave="beforeLeave">
    <slot/>
  </transition>
</template>

<script>
  let previousPosition = {}

  export default {
    name: "parent-change-transition",
    props: {
      idPropertyName: { type:String, default: 'id' },
      duration: { type:Number, default: 300 }
    },
    data () {
      return { intervalIndex: null }
    },
    mounted () {
      this.startPositionInspection()
    },
    computed: {
      childId () {
        return this[[this.idPropertyName]]
      }
    },
    methods: {
      startPositionInspection () {
        let p = this.$el.getBoundingClientRect()
        this.intervalIndex = setInterval(() => {
          let current = this.$el.getBoundingClientRect()
          if (current.x === p.x && current.y === p.y) return
          this.move(p, current)
        }, 10)
      },
      move (previous, current) {
        clearInterval(this.intervalIndex)
        this.$el.animate([
          { transform: `translate(${previous.x - current.x}px, ${previous.y - current.y}px)` },
          { transform: 'translate(0, 0)' }
        ], { duration: this.duration }).addEventListener('finish', () => this.startPositionInspection())
      },
      beforeLeave (el) {
        previousPosition[this.childId] = el.getBoundingClientRect()
      },
      beforeEnter (el) {
        el.hidden = true
      },
      afterEnter (el) {
        el.hidden = false
        if (!previousPosition[this.childId]) return
        this.move(previousPosition[this.childId], el.getBoundingClientRect())
      }
    }
  }
</script>

使用例のコードなど: https://github.com/inamori/vue-parent-change-transition

npm

https://www.npmjs.com/package/vue-parent-change-transition
せっかくなので登録しました。


  1. 「カード」をフラットに並べて自分で座標管理すればtransitionでも良いですが、それは面倒くさいからやりたくない、という背景です 

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
4
Help us understand the problem. What is going on with this article?