vue:transition过渡动画-仿微信的滑入滑出效果 & 自定义滑动指令

1,494 阅读3分钟

一、先画下页面

动画.gif

1、在views下新建4个页面

Home.vue

<template>
  <div class="home">首页</div>
</template>
<style lang="less" scoped>
.home {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(deeppink, 0.2);
}
</style>

Address.vue

<template>
  <div class="address">通讯录</div>
</template>
<style lang="less" scoped>
.address {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(deepskyblue, 0.2);
}
</style>

Discover.vue

<template>
  <div class="discover">发现</div>
</template>
<style lang="less" scoped>
.discover {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(darkgreen, 0.2);
}
</style>

Mine.vue

<template>
  <div class="mine">我的</div>
</template>
<style lang="less" scoped>
.mine {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(peru, 0.2);
}
</style>

2、路由整一下

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  { path: '/', redirect: { name: 'home' } },
  {
    path: '/home',
    name: 'home',
    component: () => import(/* webpackChunkName: "home" */ '@/views/Home'),
    meta: { index: 0 }
  },
  {
    path: '/address',
    name: 'address',
    component: () =>
      import(/* webpackChunkName: "address" */ '@/views/Address'),
    meta: { index: 1 }
  },
  {
    path: '/discover',
    name: 'discover',
    component: () =>
      import(/* webpackChunkName: "discover" */ '@/views/Discover'),
    meta: { index: 2 }
  },
  {
    path: '/mine',
    name: 'mine',
    component: () => import(/* webpackChunkName: "mine" */ '@/views/Mine'),
    meta: { index: 3 }
  }
]

const router = new VueRouter({ routes })

export default router

3、App.vue

<template>
  <div id="app">
    <router-view class="routerView" />
    <ul class="menus">
      <li
        v-for="item of menus"
        :key="item.path"
        @click="handleToggle(item.path)"
      >
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  data() {
    return {
      menus: [
        { name: '首页', path: '/home' },
        { name: '通讯录', path: '/address' },
        { name: '发现', path: '/discover' },
        { name: '我的', path: '/mine' }
      ],
      slideName: ''
    }
  },
  methods: {
    handleToggle(path) {
      if (this.$route.path === path) return
      this.$router.push({ path })
    }
  }
}
</script>

<style lang="less" scoped>
#app {
  height: 100vh;
  position: relative;
  overflow: hidden;
  .routerView {
    width: 100%;
    height: calc(100% - 36px);
  }
  .menus {
    position: absolute;
    width: 100vw;
    bottom: 0;
    display: flex;
    align-items: center;
    border-top: 1px solid #ccc;
    border-left: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
    li {
      cursor: pointer;
      width: 25%;
      text-align: center;
      line-height: 36px;
      border-right: 1px solid #ccc;
    }
  }
}
</style>

二、加入过渡动画

App.vue

    <transition :name="slideName">
      <router-view class="routerView" />
    </transition>

监听路由,动态设置过渡动画的类名

  watch: {
    $route(to, from) {
      if (to.meta.index > from.meta.index) {
        this.slideName = 'slide-left'
      } else {
        this.slideName = 'slide-right'
      }
    }
  },

或者改下handleToggle方法

    handleToggle(path) {
      if (this.$route.path === path) return
      const currentIndex = this.$route.meta.index
      const toIndex = this.menus.findIndex(n => n.path === path)
      this.slideName = toIndex > currentIndex ? 'slide-left' : 'slide-right'
      this.$router.push({ path })
    }

添加动画样式

  // 动画-start
  .slide-left-leave-active,
  .slide-left-enter-active,
  .slide-right-leave-active,
  .slide-right-enter-active {
    transition: all 0.5s;
    position: absolute;
  }
  .slide-right-leave,
  .slide-right-enter-to {
    transform: translateX(0);
  }
  .slide-right-leave-to {
    transform: translateX(100%);
  }
  .slide-right-enter {
    transform: translateX(-100%);
  }
  .slide-left-leave,
  .slide-left-enter-to {
    transform: translateX(0);
  }
  .slide-left-leave-to {
    transform: translateX(-100%);
  }
  .slide-left-enter {
    transform: translateX(100%);
  }
  // 动画-end

效果:

动画.gif

动画速度搞慢点看下DOM结构:

动画.gif

三、左右滑动也加上动画

1、安装依赖

npm install vue-directive-touch

2、main.js

import touch from 'vue-directive-touch'
Vue.use(touch)

3、给router-view加上滑动事件

    <transition :name="slideName">
      <router-view
        v-touch:left="onSwipeLeft"
        v-touch:right="onSwipeRight"
        class="routerView"
      />
    </transition>

事件类型:

  1. 单击: v-touch:tap
  2. 长按: v-touch:long
  3. 左滑: v-touch:left
  4. 右滑: v-touch:right
  5. 上滑: v-touch:up
  6. 下滑: v-touch:down

4、滑动事件方法

    // 向左滑动
    onSwipeLeft() {
      const currentIndex = this.$route.meta.index
      if (currentIndex < 3) {
        const { path } = this.menus[currentIndex + 1]
        console.log('向左滑动', currentIndex, path)
        this.$router.push({ path })
        this.slideName = 'slide-left'
      } else {
        console.log('已经是最后一页')
      }
    },
    // 向右滑动
    onSwipeRight() {
      const currentIndex = this.$route.meta.index
      if (currentIndex > 0) {
        const { path } = this.menus[currentIndex - 1]
        console.log('向左滑动', currentIndex, path)
        this.$router.push({ path })
        this.slideName = 'slide-right'
      } else {
        console.log('已经是第一页')
      }
    }

效果:

动画.gif

很丝滑~

四、自定义滑动指令

自定义滑动指令,替代vue-directive-touch

src/directives/v-touch.js

class VueTouch {
  constructor(el, binding, type) {
    // 触屏函数
    var _this = this
    this.obj = el
    this.binding = binding
    this.touchType = type
    this.vueTouches = { x: 0, y: 0 } // 触屏坐标
    this.vueMoves = true
    this.vueLeave = true
    this.vueCallBack =
      typeof binding.value == 'object' ? binding.value.fn : binding.value
    this.obj.addEventListener(
      'touchstart',
      function (e) {
        _this.start(e)
      },
      false
    )
    this.obj.addEventListener(
      'touchend',
      function (e) {
        _this.end(e)
      },
      false
    )
    this.obj.addEventListener(
      'touchmove',
      function (e) {
        _this.move(e)
      },
      false
    )
  }
  start(e) {
    // 监听touchstart事件
    this.vueMoves = true
    this.vueLeave = true
    this.longTouch = true
    this.vueTouches = {
      x: e.changedTouches[0].clientX,
      y: e.changedTouches[0].clientY
    }
    // console.log('targetTouches', e.targetTouches[0])
    this.time = setTimeout(
      function () {
        if (this.vueLeave && this.vueMoves) {
          this.touchType == 'longtap' && this.vueCallBack(this.binding.value, e)
          this.longTouch = false
        }
      }.bind(this),
      1000
    )
  }
  end(e) {
    // 监听touchend事件
    // console.log('this.vueTouches', this.vueTouches)
    // console.log('this.changedTouches', e.changedTouches[0].clientX)
    // 此处代码用来解决IOS回弹问题 begain
    if (e.changedTouches[0].clientX < 0) {
      return
    }
    // 此处代码用来解决IOS回弹问题 end
    var disX = e.changedTouches[0].clientX - this.vueTouches.x // 计算移动的位移差
    var disY = e.changedTouches[0].clientY - this.vueTouches.y
    // console.log(Math.abs(disX), Math.abs(disY))

    clearTimeout(this.time)
    if (Math.abs(disX) > 10 || Math.abs(disY) > 100) {
      // 当横向位移大于10,纵向位移大于100,则判定为滑动事件
      this.touchType == 'swipe' && this.vueCallBack(this.binding.value, e) // 若为滑动事件则返回
      if (Math.abs(disX) > Math.abs(disY)) {
        // 判断是横向滑动还是纵向滑动
        if (disX > 30) {
          this.touchType == 'swiperight' &&
            this.vueCallBack(this.binding.value, e) // 右滑
        }
        if (disX < -30) {
          this.touchType == 'swipeleft' &&
            this.vueCallBack(this.binding.value, e) // 左滑
        }
      } else {
        if (disY > 30) {
          this.touchType == 'swipedown' &&
            this.vueCallBack(this.binding.value, e) // 下滑
        }
        if (disY < -30) {
          this.touchType == 'swipeup' && this.vueCallBack(this.binding.value, e) // 上滑
        }
      }
    } else if (this.longTouch && this.vueMoves) {
      this.touchType == 'tap' && this.vueCallBack(this.binding.value, e)
      this.vueLeave = false
    }
  }
  move(e) {
    // 监听touchmove事件
    this.vueMoves = false
  }
}
export default VueTouch

src/directives/index.js

import Vue from 'vue'
import VueTouch from './v-touch'

const touchs = [
  { directive: 'ztap', touchType: 'tap' }, // 点击
  { directive: 'zswipe', touchType: 'swipe' }, // 滑动
  { directive: 'zswipeleft', touchType: 'swipeleft' }, // 左滑
  { directive: 'zswiperight', touchType: 'swiperight' }, // 右滑
  { directive: 'zswipedown', touchType: 'swipedown' }, // 下滑
  { directive: 'zswipeup', touchType: 'swipeup' }, // 上滑
  { directive: 'zlongtap', touchType: 'longtap' } // 长按
]
for (const item of touchs) {
  Vue.directive(item.directive, {
    bind(el, binding) {
      new VueTouch(el, binding, item.touchType)
    }
  })
}

main.js

import './directives'

App.vue中可以使用v-zswipeleft指令:

      <!-- <router-view
        v-touch:left="onSwipeLeft"
        v-touch:right="onSwipeRight"
        class="routerView"
      /> -->
      <!-- 可改为 -->
      <router-view
        v-zswipeleft="onSwipeLeft"
        v-zswiperight="onSwipeRight"
        class="routerView"
      />

如果你觉得这篇文章对你有用,可以看看作者封装的库xtt-utils,里面封装了非常实用的js方法。如果你也是vue开发者,那更好了,除了常用的api,还有大量的基于element-ui组件库二次封装的使用方法和自定义指令等,帮你提升开发效率。不定期更新,欢迎交流~