[DAY17]跟 Vue.js 认识的30天 - Vue 过渡(转场)及动画效果下篇(`<transition-group>`) - 多个元素的过渡及列表过渡

这一篇就来接续写完 Vue 过渡及动画的内容啦!希望能顺利写完我的 Vue 笔记。

多个元素的切换转场

以後就用 .v-*.transitionName-* 取代 .*-enter.*-enter-active.*-enter-to 等等。

先来看一下简单的切换范例

<button @click="toggle=!toggle">toggle</button>
<transition name="toggleTransition">
  <div v-if="toggle">我是A</div>
  <div v-else>我是B</div>
</transition>

<style>
.toggleTransition-enter,
.toggleTransition-leave-to {
  opacity: 0;
}
.toggleTransition-enter-active,
.toggleTransition-leave-active {
  transition: all 1s;
}
.toggleTransition-enter-to,
.toggleTransition-leave {
  opacity: 1;
}
</style>

在上面这个范例中,会发现没办法产生出转场效果,如同之前提到过的 Vue 为了渲染效能的问题,会在切换相同元素标签的情况下仅替换该元素标签内部的内容,也因此在上方的例子中, Vue 在切换时仅会切换标签内的文字,而非整个元素 <div> ,而因为这些 class .v-*.transitionName-* 都是加在元素上的,而非文字,所以在只有文字被重新渲染的状况下是不会有转场效果的。

详细的元素复用可以参考 DAY06 | 跟 Vue.js 认识的30天 - Vue 的条件渲染(v-ifv-show)

要怎麽让切换是有动画效果的呢?

第一种 - 加入 key 属性

<button @click="toggle=!toggle">toggle</button>
<transition name="toggleTransition">
  <div v-if="toggle" key="A">我是A</div>
  <div v-else key="B">我是B</div>
</transition>

第二种 - 不同标签

<button @click="toggle=!toggle">toggle</button>
<transition name="toggleTransition">
  <div v-if="toggle">我是A</div>
  <p v-else>我是B</p>
</transition>

上面 2 种方法都是同一个目的就是让 Vue 知道这 2 个标签是不同的,所以在切换时,要从元素那就替换掉,而非仅替换文字。

另外在文件中也提及了利用属性 key 结合 v-bind 或是 computed 来切换元素,道理跟上面是相同的,就是利用属性 key 的改变来形成不同的元素并得到动画效果。

<transition name="toggleTransition">
  <!--利用 toggle 的布林值来形成 save 跟 edit 2种不同的元素-->
  <button :key="toggle">{{toggle ?'save' : 'edit'}}</button>
</transition>

过渡(转场)模式

在上面的范例中会发现元素在切换的过程中,会有一个正在消失,另一个慢慢出现的过程,也因此有一段时间是 2 个元素同时存在的状况,所以有可能造成了破版的情况。

这个就可以透过加入模式 mode 来解决, Vue 提供了 2 种模式可供选择:

  • mode='in-out' :新元素先进入,完成後旧元素在离开。

  • mode='out-in' :旧元素先离开,完成後新元素在进入。

同样使用上方例子,但来达成旧元素先离开,新元素在进入的效果(mode='out-in')。

<button @click="toggle=!toggle">toggle</button>
<transition name="toggleTransition" mode='out-in'>
  <div v-if="toggle" key="A">我是A</div>
  <div v-else key="B">我是B</div>
</transition>

模组过渡的方法很简单,就是在动态组件外边加入 <transition> 标签并加入 .v-*.transition-* 等 class 样式即可。

想了解动态组件可以先参考 DAY15 | 跟 Vue.js 认识的30天 - Vue 动态模组(Dynamic Components)

列表过渡(转场)

先来知道文件中所说的,如何同时渲染整个列表,这时候要使用的是 <transition-group>

<transition-group><transition> 有几点不同及要注意的地方:

  • <transition-group> 在渲染成 DOM 是会产生出一个 <span> 元素的,但可以透过属性 tag 去改变这个元素。

  • 不能使用转场模式的属性(mode)。

  • 内部元素需要提供属性 key

  • .v-*.transition-* 等 class 样式是加在作用的那个元素上。

先来看看下面基础的列表转场范例 - 新增或移除数字

<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
  <span v-for="num in numGroup" v-bind:key="num" class="list-item">
    {{ num }}
  </span>
</transition-group>
<style>
.list-enter, .list-leave-to {
  opacity: 0;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter-to, .list-leave {
  opacity: 1;
}
</style>
<script>
const vm = new Vue({
  el: "#vm",
  data: {
    numGroup: [1, 2, 3, 4, 5, 6],
    nextNum: 7
  },
  methods: {
    add() {
      this.numGroup.splice(
        _.random(0, this.numGroup.length-1),
        0,
        this.nextNum++ // i++ 会先回传原本的值,之後才将值设定为+1
      );
    },
    remove() {
      this.numGroup.splice(
        _.random(0, this.numGroup.length-1),
        1);
    }
  }
});
</script>

这时候会发现再新增或移除过後,新数字阵列会瞬间移动到新位置,而没有动画感。如果要让新数字阵列缓慢的移到新位置,那麽可以透过 class .v-move.transitionName-move 来设定移动速度。

<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
  <span v-for="num in numGroup" v-bind:key="num">
    {{ num }}
  </span>
</transition-group>
<style>
.list-enter, .list-leave-to {
  opacity: 0;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter-to, .list-leave {
  opacity: 1;
}
.list-move {
  transition: all 3s;
}
</style>
<script>
const vm = new Vue({
  // 同上
});
</script>

使用了 .v-move.transitionName-move ,却发现没有任何变化,在 Vue 文件中有提到过:

.v-move.transitionName-move 是用FLIP达成动画位移效果的。

需要注意的是使用 FLIP 过渡的元素不能设置为 display: inline 。作为替代方案,可以设置为 display: inline-block 或者放置于 flex 中。

又来一个问题,那为什麽不能使用 display: inline 呢?先来看看FLIP文件内容:

https://ithelp.ithome.com.tw/upload/images/20210128/201275532UhgDr5VFC.png

简单扫过会发现,FLIP有使用到 transform ,但是 display: inline 是无法使用 transform 效果的(可以参考Terminology - transformable element),也因此如果要使用 .v-move.transitionName-move 的元素就不能是 display: inline

也因此针对上方范例有 2 种解决办法:

  • 改变 <transition-group> 内的标签,不要使用 <span> 等行内元素。

  • 改变 <transition-group> 内的标签属性值为 display: inline-blockdisplay: block

<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<!-- 改成 p 或者改变 CSS 属性值-->
  <span v-for="num in numGroup" v-bind:key="num">
    {{ num }}
  </span>
</transition-group>
<style>
span {
  display: inline-block;
}
.list-enter, .list-leave-to {
  opacity: 0;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter-to, .list-leave {
  opacity: 1;
}
.list-move {
  transition: all 3s;
}
</style>

变更元素或是加入 display: inline-block 後,会发现在加入数字时会有缓慢移动的动画出现,但是在移除数字时还是不能出现这个效果,在 Vue 文件的范例中有提供一个方法来让移除数字时能有动画出现,那就是在 .list-leave-active 中加入 position:absolute 就可以解决了。

就查询网上资料,如 stackoverflowGitHub,我认为是在移除的过程中(不加入 position:absolute )因为该移除元素(称元素 A )仍然是有占位的,直到动画消失这个元素才会被真正移除,但在移除数字动画开始之初,就会先透过 FLIP 计算元素 A 後面的元素需要移动多少位置,以便在这些後面元素中加入 .v-move ,但因为元素 A 是占位的,所以 FLIP 会认为这些後面元素是不需移动的,所以并不会为这些元素加入 .v-move

https://ithelp.ithome.com.tw/upload/images/20210128/20127553MkxFxqB6cd.png

https://ithelp.ithome.com.tw/upload/images/20210128/20127553J6CTzLMX0l.png

为了证实想法,是否加入 position:absolute 就是为了让元素 A 成为不占位的元素,以便让 FLIP 替後边元素计算位移距离,所以利用另一个属性 float 来试试,发现也是成功的,结论就是只要找到方法让该被移除元素在被移除之初就成为不占位的元素即可让 FLIP 动画成功。

动态过渡(转场)

这里利用 v-bind:name 来做一个切换动画效果的范例。

<button v-on:click="show=!show">toggle</button>
<button v-on:click="changeTransition">改变动画</button>
<p>{{ transitionSwiper }}</p>
<transition :name="transitionSwiper">
  <p v-if="show">Hello</p>
</transition>
<style>
/* animateA */
.animateA-enter, .animateA-leave-to {
  opacity: 0;
}
.animateA-enter-active, .animateA-leave-active{
  transition: all 3s;
}
.animateA-enter-to, .animateA-leave {
  opacity: 1;
}
/* animateB */
.animateB-enter, .animateB-leave-to {
  font-size: 13px;
}
.animateB-enter-active, .animateB-leave-active{
  transition: all 3s;
}
.animateB-enter-to, .animateB-leave {
  font-size: 30px;
}
</style>
<script>
const vm = new Vue({
  el: "#vm",
  data: {
    show: false,
    transitionSwiper: "animateA"
  },
  methods: {
    changeTransition(){
      this.transitionSwiper=this.transitionSwiper=='animateA' ? 'animateB' : 'animateA'
    }
  }
});
</script>

当按下改变动画的按钮後, <transition> 的属性 name 就会改变,也因此动画的样式也会随着改变,如上方范例,原本是套用 animateA 的动画效果,经过改变後会套用 animateB 的效果。

Demo:DAY17 | 跟 Vue.js 认识的30天 - Vue 过渡及动画效果下篇 - 多个元素的过渡及列表过渡

参考资料:

Vue.js - 进入/离开 & 列表过渡

stackoverflow

GitHub


<<:  [DAY16]跟 Vue.js 认识的30天 - Vue 过渡(转场)及动画效果上篇(`<transition>`)

>>:  [DAY18]跟 Vue.js 认识的30天 - Vue 混入(`mixin`)

Day 15 Compose LazyRow

今年的疫情蛮严重的,希望大家都过得安好,希望疫情快点过去,能回到一些线下技术聚会的时光~ 今天目标:...

D10. 学习基础C、C++语言

D10: 简单的练习UVA(11805) #include <stdio.h> #inc...

Day08 SwiftUI 01 - Life Cycle : UIKit App Delegate

在 2019 年时,Apple 推出了两套强大的框架分别是 SwiftUI 以及 Combine,而...

GitHub Action Automation - 自动化你的管理程序与使用第三方 Action

过去的我,一提到 GitHub Action 就直接联想到持续整合与布署,然後就开始进入如何设计、撰...

第29天:『SEO优化第十一步』-从Jetpack的统计资料进行流量侦测

SEO优化-流量侦测 当网站开始运行後,就可以利用WordPress外挂Jetpack的统计资料功能...