transition-group 原理详解

transition-group 组件不同于 transition 组件会渲染成一个真实元素,如果我们没有定义 tag 属性,默认为 span。具备 transition 所有特性,并且有自己独有的 render 方法:

1、rawChildren 是存储默认插槽值;

2、preChildren 是存储上一次子节点(因为节点可以被添加和删除);

3、children 是存储当前的含有 key 的子节点;

4、通过 extractTransitionData 方法从 transition-group 的属性和事件中获取 transitionData 数据;

5、遍历 rawChildren,只有有 key 的节点添加到 children 中(这就是 transition-group 的每个子节点必须要有 key 的原因,即便是使用 v-for 也必须要有 key);

6、在每个当前的子节点的 data 属性中添加 transition 属性,值为 transitionData;

7、如果有 preChildren,那么就遍历它,然后保留上一次每个子节点的位置(为平滑移动作记录最开始的位置),将两次都会渲染的子节点添加到 kept 数组中,将只有上一次渲染的子节点添加到 removed 数组中;

8、通过 createElement 函数创建一个渲染 vnode(tag 默认为 span),将 kept 作为子节点参数传入,将值复制给 this.kept 中;

9、最终 render 会返回一个 :通过 createElement 函数创建一个渲染 vnode,将 children 数组作为子节点参数传入。

transition-group 内置了 FLIP 动画(最终态未知的情况下),因此当我们添加或者删除一个节点时,其他节点会平滑移动,而不是生硬移动:

扫描二维码关注公众号,回复: 15285484 查看本文章

F 为 First,即记录元素的最初态,在元素移动之前记录;

L 为 Last 即记录元素要移动到的最终态,在元素移动后记录;

I 为 Invert 即先计算最初态和最终态的差值,然后通过 transfrom 将元素移动到最初态(将差值传入给 transform),在这一步之后要进行一次强制重绘,由于在渲染节点时会经历几个过程(进行计算),在一个同步队列中,在重排计算到这个位置时,就要触发重绘将这个位置的元素呈现出来,否则只会呈现一个 tick 中最后的计算结果;

p 为 play 即动画缓动的过程,先用 transition 监听 transform 或者 all 的变化,然后将 transform 属性设置为 0,我们就能看到缓动的发生。

想要彻底理解上面所说的知识,自己谷歌 FLIP 原理,然后看这篇文章:重排和重绘

触发重绘的方式除了文中所说的方式外(同步过程),我们还可以用异步的方式(因为不在同一个 tick),一般会使用 requestAnimationFrame 来呈现动画(在下一帧中执行代码),当然也可以用 setTimeout 等异步的方式,但是第一种方式是不会有卡顿的现象发生,原因在这里:requestAnimationFrame 详解

在我们的示例中,节点的删除和添加也会发生上下缓动,这是因为在添加下面的两个 class 时,是在 transition 组件特性中的由 requestAnimationFrame 封装的函数中执行的:

.list-complete-enter-active {   transition: all 1s; }
.list-complete-leave-active {   transition: all 1s;   position: absolute; }

下面来看看 transiton-group 是怎么做到 moveClass 的特性的,核心代码在 transition-group 组件中的 update 钩子中(在修改数据时就会触发这个函数):

1、获取 moveClass,如果没有在 transition-group 组件节点中定义 moveClass 属性,那么就在 name 属性值后面拼接 move;

2、如果没有子节点或者检测到子节点没有通过 transition 监听 transform 或者 all(通过 hasMove 函数来检测),那么就直接 return;

3、遍历 children 执行 callPendingCbs 函数,由于整个过渡是异步的,在上一个过渡结束之前,如果又触发了 updated 钩子,那么就立即执行上一个 moveCb 或者 enterCb 动画结束的回调函数,确保整个执行是没有问题的;

3、第二次遍历 children 执行 recordPosition 函数,保留当前每个子节点的位置(为平缓移动记录最终态);

4、第三次遍历 children 执行 applyTranslation 函数,计算每个子节点的最初态和最终态的差值,如果差值不为零,那么通过 transfrom 立即将元素从最终态移动到最初态,并且将这个节点的 data.move 设置成 true,表示这个节点需要缓动;

5、通过 offsetHeight 强制执行重绘,那么节点在最初态就会呈现出来(同一个 tick 中只会呈现出最终计算的位置);

6、再次遍历 children,给需要缓动的子节点添加 moveClass(使节点带有 transition class 属性,监听 transform 或者 all 的变化),移除 transform class 属性,添加 transitionEndEvent 事件,回调函数为 moveCb,清除 transitionEndEvent 事件、移除 moveClass、将 el._moveCb 设置为 null,表示回调函数已经被执行了。

因此想让 transition-group 组件的子节点有缓动效果有三种方式:1、给 transiiton-group 组件节点添加 moveClass 属性,自定义 class 名,然后在 style 中给该 class 添加 transiiton;2、直接在 style 中给 name + 'move' 拼接的 class 添加 transition;3、在每个子节点中添加一个自定义的 class 名,然后在 style 中给该 class 添加 transition。

然后要注意,子节点的 display 不能为 inline,如果默认是 inline 必须写成 inline-block,这样 FLIP 动画才会生效,这是 Vue 官网中特别提醒的。

最后讲下 transition-group 组件的 beforeMount 钩子,由于 updateChildren 函数会对元素进行移动,最终导致删除的节点很可能已经不在原来的位置上,所以重写了 _update 函数,执行了两次 patch 函数,第一次 patch 只删除所有需要删除的节点,不作移动(将 removeOnly 参数设置为 true),然后触发删除节点的 leaving 过渡(如此删除的节点在执行动画时,位置是正确的);第二次 patch 就插入新节点或者移动节点到最终态。

那么到此,transition-group 组件的实现原理就介绍完毕了,它和 transition 组件相比,实现了列表的过渡,以及它会渲染成真实的元素。当我们去修改列表的数据的时候,如果是添加或者删除数据,则会触发相应元素本身的过渡动画,这点和 transition 组件实现效果一样,除此之外 transition-group 还实现了 move 的过渡效果,让我们的列表过渡动画更加丰富。

参考自transition-group - 知乎

猜你喜欢

转载自blog.csdn.net/coinisi_li/article/details/126886026