该系列用于记录一些使用方法、demo 以及原理分析。本文介绍使用 Javascript 钩子来实现动画效果的过程。

# JavaScript 钩子

# 完整的钩子信息

这里我们直接贴出官网 (opens new window)的展示:

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"
  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>
// ...
methods: {
  // --------
  // 进入中
  // --------

  beforeEnter: function (el) {
    // ...
  },
  // 此回调函数是可选项的设置
  // 与 CSS 结合时使用
  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },

  // --------
  // 离开时
  // --------

  beforeLeave: function (el) {
    // ...
  },
  // 此回调函数是可选项的设置
  // 与 CSS 结合时使用
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  // leaveCancelled 只用于 v-show 中
  leaveCancelled: function (el) {
    // ...
  }
}

这里对进入和离开的动画钩子说明也比较清晰了,我们来看个 demo:

# 使用 jQuery 的例子

官方提供一个使用 Velocity.js 的例子,这块本骚年不熟悉,就直接拿个最简单的 jQuery 动画来写把~

<div id="example-4">
  <button @click="show = !show">
    Toggle
  </button>
  <transition v-on:enter="enter" v-on:leave="leave" v-bind:css="false">
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>
new Vue({
  el: "#example-4",
  data: {
    show: false
  },
  methods: {
    enter: (el, done) => {
      // 元素已被插入 DOM
      // 在动画结束后调用 done
      $(el)
        .css("opacity", 0)
        .animate({ opacity: 1, fontSize: "100px" }, 1000, done);
    },
    leave: (el, done) => {
      // 与 enter 相同
      $(el).animate({ opacity: 0, fontSize: "0px" }, 1000, done);
    }
  }
});

可以看看这里的 demo (opens new window)

image

# 初始渲染

Vue 的动画提供了一个初始渲染的开关,指的是第一次展示(而不是第一次切换)的时候是否需要动画效果。

具体可以看看上面的例子,当我们把 show 的默认值设置为 true 的时候,初次展示并不会加载进入动画。

但是当我们在 transition 组件中添加 appear 属性时,则在初次展示时也会加载进入动画:

<div id="example-4">
  <button @click="show = !show">
    Toggle
  </button>
  <transition appear v-on:enter="enter" v-on:leave="leave" v-bind:css="false">
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>

可以查看demo (opens new window),尝试将 appear 去掉试试(demo (opens new window)),看看区别就能理解了~

当然,你也可以绑定 appear 专属的动画效果:

<transition
  appear
  v-on:before-appear="customBeforeAppearHook"
  v-on:appear="customAppearHook"
  v-on:after-appear="customAfterAppearHook"
  v-on:appear-cancelled="customAppearCancelledHook"
>
  <!-- ... -->
</transition>

# 实现逻辑

上一节1. transition 组件》中,我们详细描述了在使用 CSS 过渡和动画的时候,具体的实现原理。

而 Javascript 钩子的实现逻辑其实很简单:

  1. 检测组件是否使用了 Javascript 钩子。
  2. 若使用了 Javascript 钩子,则这些钩子函数将在恰当的时机被调用。

当然这些钩子分别穿插在各个地方,顺序的话也很明了。这里以离开动画为例子,我们来看看这段被我简化后的代码:

export function leave(vnode: VNodeWithData, rm: Function) {
  // el是被动画的元素
  const el: any = vnode.elm;

  // 当然如果进入动画还没结束,就取消吧~
  // call enter callback now
  if (isDef(el._enterCb)) {
    el._enterCb.cancelled = true;
    el._enterCb();
  }

  // 嗯,这里获取该获取的东西,包括Javascript钩子和CSS类名
  const data = resolveTransition(vnode.data.transition);
  const {
    css,
    type,
    leaveClass,
    leaveToClass,
    leaveActiveClass,
    beforeLeave,
    leave,
    afterLeave,
    leaveCancelled,
    delayLeave,
    duration
  } = data;

  // 这个cb比较重要啦,将
  const cb = (el._leaveCb = once(() => {
    // 移除v-leave-to和v-leave-active类名
    if (expectsCSS) {
      removeTransitionClass(el, leaveToClass);
      removeTransitionClass(el, leaveActiveClass);
    }

    if (cb.cancelled) {
      // 如果离开被取消,则移除v-leave类名
      if (expectsCSS) {
        removeTransitionClass(el, leaveClass);
      }
      // 看到了吧,这里埋下了leaveCancelled的Javascript钩子
      leaveCancelled && leaveCancelled(el);
    } else {
      rm();
      // 这里埋下了afterLeave的Javascript钩子
      afterLeave && afterLeave(el);
    }
    // 这个离开动画的函数只调用一次啦
    el._leaveCb = null;
  }));

  // 看看要不要延迟执行啦
  if (delayLeave) {
    delayLeave(performLeave);
  } else {
    performLeave();
  }

  // 嗯,这个才是真正执行的函数
  function performLeave() {
    // the delayed leave may have already been cancelled
    if (cb.cancelled) {
      return;
    }

    // 看,这里埋下了beforeLeave的Javascript钩子
    beforeLeave && beforeLeave(el);
    // 下面这是上一节讲到的逻辑,执行CSS过渡动画的
    // cb是上面定义的回调啦,里面包括afterLeave钩子
    if (expectsCSS) {
      addTransitionClass(el, leaveClass);
      addTransitionClass(el, leaveActiveClass);
      nextFrame(() => {
        addTransitionClass(el, leaveToClass);
        removeTransitionClass(el, leaveClass);
        if (!cb.cancelled && !userWantsControl) {
          if (isValidDuration(explicitLeaveDuration)) {
            setTimeout(cb, explicitLeaveDuration);
          } else {
            whenTransitionEnds(el, type, cb);
          }
        }
      });
    }

    // 看,这里还埋下了leave的Javascript钩子
    // 和其他钩子不同的是,这里传进去cb了
    leave && leave(el, cb);
    if (!expectsCSS && !userWantsControl) {
      cb();
    }
  }
}

嗯,大概是这些的逻辑啦,现在大家明白了leave: function (el, done)里的 done 是什么了吧~

# 结束语

本节我们介绍了 Vue 动画中的 Javascript 钩子,以及相关的一些源码分析。很多时候我们看一个框架内容好多好复杂,其实慢慢理解和思考,一点点拆分下来也不是特别难的啦。

部分文章中使用了一些网站的截图,如果涉及侵权,请告诉我删一下谢谢~
温馨提示喵