服务器之家:专注于服务器技术及软件下载分享
分类导航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服务器之家 - 编程语言 - JavaScript - vue.js - 如何在Vue中实现Svelte的Defer Transition

如何在Vue中实现Svelte的Defer Transition

2022-02-28 16:17Jayden.李 vue.js

这篇文章主要介绍了如何在Vue中实现Svelte的Defer Transition,帮助大家更好的理解和学习使用vue,感兴趣的朋友可以了解下

最近观看了Rich Harris的<Rethinking Reactivity>视频,惊叹于Svelte框架的高效同时,还发现了Vue所不具备的一些关于动画的原生支持—defer transitions.

先看看Svelte所谓的defer transition的效果吧。

如何在Vue中实现Svelte的Defer Transition

这是使用Svelte做的Todo Demo应用。整个todo分成3个部分。输入部分,todo列表和done列表。当点击todo列表中的条目时,相应条目会被“移动”到done列表,反之亦然。

在这里,条目从一个列表转移到另一个列表,不是很突兀的闪现,而是非常友好地从点击处,移动到目的地;同时,当列表中条目离开时,剩余的条目会丝滑地向上移动填补空缺的位置。在Svelte里,只需要仅仅加上几行代码,就能实现,对于开发者非常友好且高效。 参考如下代码或者Svelte教程

?
1
2
3
4
5
6
7
8
9
10
{#each todos.filter(t => !t.done) as todo (todo.id)}
    <label
    in:receive="{{key: todo.id}}"
        out:send="{{key: todo.id}}"
        animate:flip>
        <input type=checkbox on:change={() => mark(todo, true)}>
            {todo.description}
            <button on:click="{() => remove(todo)}">remove</button>
    </label>
{/each}

仅仅在element上加上了in、out和animate属性。这里,in和out各自接受框架提供的函数receive和send并且给他们提供了筛选条件。 animate属性接收内置的flip方法。这里的flip不是翻转,而是FLIP技术技术,vue在<transition-group>中也有用到。主要用于整体移动列表剩余条目去填补所失去元素的位置。

于是我就在想,如果是Vue的话,如何能达到相应的效果呢。 (不想看详细说明的话,可以直接查看code pen中的代码)

Vue原生提供了两个组件支持动画。transition和transition-group。由于是list的移动,所以我们这里使用transition-group。具体使用方法可以参考Vue教程Transitions & Animation。

要想达到同样的效果,有两大UI动画效果要实现。

列表中条目消失时,剩余条目移动补齐空位
条目消失同时在另外一个列表插入时,条目移动
第一个需求的实现比较简单,vue原生已经提供了良好的支持,参考Vue文档中的List-Move-Transitions即可

为了实现第二个需求,有几个问题必须解决:

  1. 消失条目的位置信息
  2. 插入条目的位置信息
  3. 动效开始与结束的时机

我们先看看前两个问题的如何解决。根据文档的介绍,transition-group提供了javascript hook。分别是:

?
1
2
3
4
5
6
7
8
9
v-on:before-enter
v-on:enter
v-on:after-enter
v-on:enter-cancelled
 
v-on:before-leave
v-on:leave
v-on:after-leave
v-on:leave-cancelled

可视化表示的话,大概是如下图所示:

如何在Vue中实现Svelte的Defer Transition

before-enter: 用于设置插入条目的transition的初始值。此时无法获取BoundingClientRect. enter: 动效期。此时enter钩子函数的入参el能获取boundingClientRect after-enter: 动效结束后的回调函数 enter-cancelled: 取消enter的钩子

leave也是类似。

所以,我们能拿到条目元素DOMRect信息的时机只有enter和leave的时候。

这样,我们就可以在leave时候,拿到leave条目的DOMRect数据并且保存起来。在enter的时候, 我们就能同时拥有leave条目和enter条目的位置信息了。

位置信息是拿到了,那怎么才能在条目进入的时候,有从消失条目移动过来的效果呢。(可以先想想, 再看后面的解释)。

所以,我们想要达成移动的效果,首先需要隐藏掉leave条目元素,

?
1
2
3
4
5
6
leave(el, done) {
      console.log("before leave");
      const rect = el.getBoundingClientRect();
      sendRectMap.set(el.dataset.key, rect);
      el.style.display = "none";
},

然后给enter条目元素设定关于位置初始状态,初始化的位置即为leave条目元素的位置,然后当transition开始生效的时候,让其位置恢复到插入(enter)的位置。

这种方法其实就是所谓的FLIP技术。transition-group组件里也使用了这种技术来移动剩余列表填充移走条目空白。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var first = el.getBoundingClientRect();
 
// Now set the element to the last position.
el.classList.add('totes-at-the-end');
 
// Read again. This forces a sync
// layout, so be careful.
var last = el.getBoundingClientRect();
 
// You can do this for other computed
// styles as well, if needed. Just be
// sure to stick to compositor-only
// props like transform and opacity
// where possible.
var invert = first.top - last.top;
 
// Invert.
el.style.transform =
    `translateY(${invert}px)`;
 
// Wait for the next frame so we
// know all the style changes have
// taken hold.
requestAnimationFrame(function() {
 
  // Switch on animations.
  el.classList.add('animate-on-transforms');
 
  // GO GO GOOOOOO!
  el.style.transform = '';
});

那么接下来的问题就是,在什么时机去设置enter条目元素transition的初始状态,在什么时机去设置enter条目元素transition的结束时状态。

按照上面提到的javascript hook,我们可以在before-enter钩子函数里设置初始状态,接着在enter钩子函数里设置transition结束状态。那么,我们的初始状态是什么呢?我们通过getBoundingClientRect,可以获取到enter元素(后用to来标识)的DOMRect数据,包括top, left, bottom, right, width, height , x, y。 同时我们也能通过之前保存的leave位置map获取到leave条目的位置信息(称为from)。所以在before-enter时,我们通过计算得到的偏移量,通过translate去初始化to元素的位置。然后再在enter阶段,translate其值到from, 再加上transition到css中即可。

在before-enter钩子中:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 移动元素的标识符
const key = el.dataset.key;
// enter条目 map,注意这里,在before-enter钩子中,
// receiveRectMap是get不到to的。需要特殊处理。后面有提及
const to = receiveRectMap.get(key);
// leave条目 map
const from = sendRectMap.get(key);
 
// 计算偏移量
const dx = from.left - to.left;
const dy = from.top - to.top;
 
// 初始化to条目的位置
el.style.transform = `translate(${dx}px, ${dy}px)`;
el.style.opacity = 0;

在enter钩子中:

?
1
2
3
4
el.style.transition = "all 800ms";
el.style.transform = "";
el.style.opacity = 1;
el.style.display = "block";

上面的代码中,在before-enter里面,to是通过receiveRectMap.get(key)来获取的。但是,这时,receiveRectMap中还没有对应key的DOMRect值。虽然,before-enter的入参是el(HTMLElement),但是该el元素的DOMRect中的所有值都为0,所以我们需要在enter方法中,把el塞入到receiveRectMap中。这样就会有一个矛盾,那就是无法在before-enter中通过translate初始化to元素的位置了。所以,我们这里使用defer transition技术,延迟transition的发生。

我们可以在enter中使用setTimeout或者requestAnimationFrame实现defer transition,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
requestAnimationFrame(() => {
        const key = el.dataset.key;
          // 这样,receiveRectMap中就有该key的值了。
        const to = receiveRectMap.get(key);
        const from = sendRectMap.get(key);
 
        const dx = from.left - to.left;
        const dy = from.top - to.top;
 
        // 由于我们延迟了transition的发生,
        // 所以to元素的位置其实已经到达了目的地位置,
        // 所以我们需要使用transition手动地将其过渡到from位置,这一行很重要
        el.style.transition = "all 0ms";
        el.style.transform = `translate(${dx}px, ${dy}px)`;
        
        // 初始化结束后,在下一个animation frame中,使用FLIP技术,使其移动回来。
    requestAnimationFrame(() => {
          el.style.transition = "all 800ms";
          el.style.transform = "";
          el.style.opacity = 1;
          el.style.display = "block";
        });
      });

完整代码可以参考codepen

最后效果:

如何在Vue中实现Svelte的Defer Transition

以上就是如何在Vue中实现Svelte的Defer Transition的详细内容,更多关于Vue 实现Svelte的Defer Transition的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/6949441502321836046

延伸 · 阅读

精彩推荐
  • vue.jsVue多选列表组件深入详解

    Vue多选列表组件深入详解

    这篇文章主要介绍了Vue多选列表组件深入详解,这个是vue的基本组件,有需要的同学可以研究下...

    yukiwu6752022-01-25
  • vue.jsVue2.x-使用防抖以及节流的示例

    Vue2.x-使用防抖以及节流的示例

    这篇文章主要介绍了Vue2.x-使用防抖以及节流的示例,帮助大家更好的理解和学习使用vue框架,感兴趣的朋友可以了解下...

    Kyara6372022-01-25
  • vue.js详解vue 表单绑定与组件

    详解vue 表单绑定与组件

    这篇文章主要介绍了vue 表单绑定与组件的相关资料,帮助大家更好的理解和学习使用vue框架,感兴趣的朋友可以了解下...

    Latteitcjz6432022-02-12
  • vue.js用vite搭建vue3应用的实现方法

    用vite搭建vue3应用的实现方法

    这篇文章主要介绍了用vite搭建vue3应用的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下...

    Asiter7912022-01-22
  • vue.js梳理一下vue中的生命周期

    梳理一下vue中的生命周期

    看过很多人讲vue的生命周期,但总是被绕的云里雾里,尤其是自学的同学,可能js的基础也不是太牢固,听起来更是吃力,那我就已个人之浅见,以大白话...

    CRMEB技术团队7992021-12-22
  • vue.jsVue中引入svg图标的两种方式

    Vue中引入svg图标的两种方式

    这篇文章主要给大家介绍了关于Vue中引入svg图标的两种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    十里不故梦10222021-12-31
  • vue.jsVue2.x 项目性能优化之代码优化的实现

    Vue2.x 项目性能优化之代码优化的实现

    这篇文章主要介绍了Vue2.x 项目性能优化之代码优化的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋...

    优小U9632022-02-21
  • vue.jsVue项目中实现带参跳转功能

    Vue项目中实现带参跳转功能

    最近做了一个手机端系统,其中遇到了父页面需要携带参数跳转至子页面的问题,现已解决,下面分享一下实现过程,感兴趣的朋友一起看看吧...

    YiluRen丶4302022-03-03