陪你写一个Vue全局拖拽组件

网站上这个代码排版我真的是醉了,附上github地址,直接跑起来看效果吧~vue

前言

大佬们,都是手摸手带大家学习,而我就摸不了大家的小手了。由于可能我分享的内容,是一些你们早已知悉的内容,只不过这个需求,挺有意思的,我实现完以后。写这个文章为了分享一些能帮助到别人的内容,也是为了归档一下本身的知识点。有写的很差的地方望指正。

需求

你们在浏览电商的 app 的时候,大多数在详情页的右下角上方,会有一个悬浮的按钮。用来分享商品详情、或者回到首页等功能的实现。咱们的项目呢是,基于小程序的一款金融平台,应用了小程序原声+webview 的方案。由于 webview 里面路由跳转是 vue 的跳转,小程序并无 navigateto,在不一样系统,机型上,小程序的 webview 实现方式有所不一样,并且页面层级增长以后。就会致使用户回退的时候,可能会点不少次才能回到小程序的原生页面。因此咱们也采用了这一方案。

实现

组件好多的内容,不少大神都有文章来写的用法概念什么,我就不一一赘述了。首先这种组件在咱们项目里面是 webview 里面全部页面都要用的,确定得是一个全局组件,若是一个个引入那真滴是太 low 了。我就直接给你们上代码了。

vue 组件

<template>  <div class="yht-float-btn pos-r" v-show="showBtn" :style="{'width':itemWidth+'px','height':itemHeight+'px','left':left+'px','top':top-70+'px'}"    ref="floatDiv">    <div class="top-icon pos-a">      <transition name="slide-fade">        <div class="float-box text-center hide-box" v-show="show" @click="handleClickItem(0)">          <!-- 问题 -->          <img src="@/assets/img/yht/icon_question.png" alt="">        </div>      </transition>      <transition name="slide-fade">        <div class="float-box text-center hide-box" v-show="show" @click="handleClickItem(1)">          <img src="@/assets/img/yht/icon_home.png" alt="">          <!-- 首页 -->        </div>      </transition>    </div>    <div class="float-box text-center pos-a bottom-icon" @click="handleClickMenu">      <div>        <img v-if="show" src="@/assets/img/yht/icon_close.png" alt="">        <img v-else src="@/assets/img/yht/icon_shortcut.png" alt="">      </div>    </div>  </div></template><script>import Util from "@/utils/common";import router from "../../router";export default {  name: "drag",  components: {},  props: {    text: {      type: String,      default: "默认文字"    },    itemWidth: {      type: Number,      default: 66    },    itemHeight: {      type: Number,      default: 66    },    gapWidth: {      type: Number,      default: 10    },    coefficientHeight: {      type: Number,      default: 0.8    },    showBtn: {      // 是否显示此toast      default: false    }  },  data() {    return {      timer: null,      currentTop: 0,      clientWidth: 0,      clientHeight: 0,      left: 0,      top: 0,      show: false    };  },  created() {    this.clientWidth = document.documentElement.clientWidth;    this.clientHeight = document.documentElement.clientHeight;    this.left = this.clientWidth - this.itemWidth - this.gapWidth;    this.top = this.clientHeight * this.coefficientHeight;  },  mounted() {    window.addEventListener("scroll", this.handleScrollStart);    this.$nextTick(() => {      const div = this.$refs.floatDiv;      div.addEventListener("touchstart", e => {        div.style.transition = "none";      });      div.addEventListener("touchmove", e => {        e.preventDefault();        if (e.targetTouches.length === 1) {          let touch = event.targetTouches[0];          this.left = touch.clientX - this.itemWidth / 2;          this.top = touch.clientY - this.itemHeight / 2;        }      });      div.addEventListener("touchend", e => {        div.style.transition = "all 0.3s";        if (this.left > this.clientWidth / 2) {          // 浮动靠右边          this.left = this.clientWidth - this.itemWidth - this.gapWidth;          console.log("这里是右边");        } else {          // 靠左边          this.left = this.gapWidth;          console.log("这里是左边");        }        if (this.top < 0) {          // 防止滑到上方被隐藏          this.top = this.clientHeight * this.coefficientHeight;        }      });    });  },  beforeDestroy() {    window.removeEventListener("scroll", this.handleScrollStart);  },  methods: {    handleScrollStart() {      this.timer && clearTimeout(this.timer);      this.timer = setTimeout(() => {        this.handleScrollEnd();      }, 300);      this.currentTop =        document.documentElement.scrollTop || document.body.scrollTop;      if (this.left > this.clientWidth / 2) {        this.left = this.clientWidth - this.itemWidth / 2;      } else {        this.left = -this.itemWidth / 2;      }    },    handleScrollEnd() {      let scrollTop =        document.documentElement.scrollTop || document.body.scrollTop;      if (scrollTop === this.currentTop) {        if (this.left > this.clientWidth / 2) {          this.left = this.clientWidth - this.itemWidth - this.gapWidth;        } else {          this.left = this.gapWidth;        }        clearTimeout(this.timer);      }    },    handleClickMenu() {      this.show = !this.show;    },    handleClickItem(type) {      if (type === 0) {        router.push({ path: "/question/question" });      } else {        if (window.sessionStorage.getItem("isMiniEnvironment") !== "0") {          Util.toMiniIndex(); // 这里是回到小程序首页        } else {          router.replace({ path: "/yht/index" });        }      }    }  }};</script><style lang="less" scoped>.yht-float-btn {  z-index: 99999;  transition: all 0.3s;  position: fixed;  bottom: 20vw;  img {    width: 66px;    height: 66px;    object-fit: contain;    margin-bottom: 3px;  }  p {    font-size: 7px;  }}.float-box {  width: 66px;  height: 66px;}.slide-fade-enter-active {  transition: all 1s ease;}.slide-fade-leave-active {  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);}.slide-fade-enter, .slide-fade-leave-to/* .slide-fade-leave-active for below version 2.1.8 */ {  transform: translateY(10px);  opacity: 0;}.top-icon {  left: 0;  bottom: 66px;}.bottom-icon {  bottom: 0;  left: 0;}</style>复制代码


这个组件写好以后 能够先 引入到一个页面里面调一下样式。

实现动态加载组件

import Float from './floatBtn.vue';let dom;const FloatBtn = {}; // 定义插件对象FloatBtn.install = function(Vue, options) {  // vue的install方法,用于定义vue插件  // 若是toast还在,则再也不执行  if (document.getElementsByClassName('yht-float-btn').length) {    return;  }  let Instance = Vue.extend(Float);  const initInstance = () => {    // 实例化vue实例    dom = new Instance();    dom.vm = dom.$mount();    let boxEl = dom.$mount().$el;    document.body.appendChild(boxEl);  };  Vue.prototype.$floatBtn = {    showFloat(options) {      if (!dom) {        initInstance();      }      dom.vm.showBtn = true; // 显示toast    },    hideFloat(options = 0) {      if (!dom) {        initInstance();      }      dom.vm.showBtn = false; // 显示总体    }  };};export default FloatBtn;复制代码

在全局中的使用

需求是,当用户进入页面栈两层以后才给用户显示这个浮动组件。 因此在路由守卫中先定义了一个计数器,在路由跳转的时候计数器增长。其实原本是想在路由守卫中来调用这个浮动组件的,可是笔者在路由守卫中,尝试调用组件的时候,发现访问不到这个组件。由于当时需求比较着急,就换了一种方式。也但愿有大佬能指点一下这块的知识点~

let count = 2; // 悬浮按钮计数器count++;window.sessionStorage.setItem('floatCount', count);// 而后在router-view页面里this.floatCount = this.$storage.get('floatCount'); // 获取悬浮按钮计数器if (this.floatCount > 3) {  this.$floatBtn.showFloat();}// 固然若是没有特别的需求的话,这个组件就能够在任意一个vue页面里面去调用  this.$floatBtn.showFloat() 这个方法。 
// 还要在mainjs里面import FloatBtn from '@/components/floatBtn/floatBtn';Vue.use(FloatBtn); // 增长 悬浮按钮
复制代码

效果以下图:


到这里就陪你写完了一个vue全局的组件,奖励本身一朵小发发~