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

1,503 阅读4分钟

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

前言

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

需求

大家在浏览电商的 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全局的组件,奖励自己一朵小发发~