# Vue transition探索

使用过Vue的小伙伴都知道,Vue的内置组件transition,在以下情况,可以给单个元素/组件添加进入/离开的过渡

  • 条件渲染(v-if)
  • 条件展示(v-show)
  • 动态组件
  • 组件根节点

这篇文章主要解密:条件展示(v-show)下,html+css+js实现一个简单的transition

先来看看vue官网文档:进入/离开&列表过渡 (opens new window)

# Vue的过渡实现步骤

1.自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名 2.如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用 3.如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行

所以真正执行动画的是我们写的css或者javascript钩子函数,而Vue的transtion只是帮我们很好地管理了这些css的添加/删除,以及钩子函数的执行时机。

# 过渡的类名

name 定义 添加时间 移除时间
v-enter 过渡的开始状态 元素被插入之前 元素被插入的下一帧移除
v-enter-active 过渡生效时候的状态 被插入之前 过渡/动画完成
v-enter-to 过渡结束状态 元素被插入之后下一帧 过渡/动画完成
v-leave 离开过渡的开始状态 离开过渡触发时刻 下一帧
v-leave-active 离开过渡生效的状态 离开过渡触发 过渡/动画完成
v-leave-to 离开过渡结束状态 离开过渡触发下一帧 过渡/动画完成

过渡/动画完成后移除class:怎么判断过渡/动画是否完成?可以监听对应的事件:

  • 过渡完成:transitionend
  • 动画完成:animationend

了解了以上知识点,实现一个简单的transition就非常的简单了。 查看demo (opens new window)效果:https://merrylmr.github.io/vue-theory-analysis/ (opens new window) 效果图

# 核心源码

  class Transition {
    constructor(el, name, show) {
      this.el = el
      this.show = show || false // el初始状态,显示or隐藏
      this.x = this.autoCssTransition(name)

      this.enterCb = function () {
        el.style.display = 'block'
      }
      this.leaveCb = function () {
        el.style.display = 'none'
      }
      this.init(el)
    }

    autoCssTransition(name) {
      return {
        enterClass: `${name}-enter`, // start-class
        enterToClass: `${name}-enter-to`, // toClass
        enterActiveClass: `${name}-enter-active`, //动画完成删除
        leaveClass: `${name}-leave`,
        leaveToClass: `${name}-leave-to`,
        leaveActiveClass: `${name}-leave-active`
      }
    }

    addTransitionClass(el, cls) {
      el.classList.add(cls)
    }

    removeTransitionClass(el, cls) {
      el.classList.remove(cls)
    }

    init(el) {
      if (!this.show) {
        el.style.display = 'none'
      }

      let {
        enterToClass,
        enterActiveClass,
        leaveToClass,
        leaveActiveClass
      } = this.x

      let cb = () => {
        if (this.show) {
          this.removeTransitionClass(el, enterToClass)
          this.removeTransitionClass(el, enterActiveClass)

        } else {
          this.removeTransitionClass(el, leaveToClass)
          this.removeTransitionClass(el, leaveActiveClass)
          this.leaveCb()
        }
      }
//     监听CSS过渡执行完成
      el.addEventListener('transitionend', cb)
//      监听css动画动画执行完成
      el.addEventListener('animationend', cb)
    }

    enter() {
      this.show = true
      this.enterCb()
      this.addTransitionClass(this.el, this.x.enterClass)
      this.addTransitionClass(this.el, this.x.enterActiveClass)
      // 下一帧
      nextFrame(() => {
        this.removeTransitionClass(this.el, this.x.enterClass)
        this.addTransitionClass(this.el, this.x.enterToClass)
      })
    }

    leave() {
      this.show = false
      this.addTransitionClass(this.el, this.x.leaveClass)
      this.addTransitionClass(this.el, this.x.leaveActiveClass)
   // 下一帧
      nextFrame(() => {
        this.removeTransitionClass(this.el, this.x.leaveClass)
        this.addTransitionClass(this.el, this.x.leaveToClass)
      })
    }

    toggle() {
      if (!this.show) {
        this.enter()
      } else {
        this.leave()
      }
    }
  }

# 简单使用

// css
 .slide-top-enter,
    .slide-top-leave-to {
      transform: translateY(-100%);
    }

    .slide-top-enter-active,
    .slide-top-leave-active {
      transition: all 0.3s ease;
    }
// js:
 let instance = new Transition(box, 'slide-top')
  btn.addEventListener('click', () => {
    instance.toggle()
  })