JS实现悬浮可拖拽vue组件封装

纯JS,无插件

需求:页面指定位置放一个悬浮小图标。可以点击拖拽改变位置。

封装了一个悬浮小组件,slot插入要显示的内容

PC端:

javascript 复制代码
<template>
  <div
    class="draggable-div"
    ref="draggable"
    :style="{
      width: width || null,
      height: height || null,
      top: top || null,
      right: right || null,
      bottom: bottom || null,
      left: left || null,
    }"
  >
    <slot></slot>
  </div>
</template>
<script>
export default {
  props: {
    left: {
      type: Number | String
    },
    right: {
      type: Number | String
    },
    bottom: {
      type: Number | String
    },
    top: {
      type: Number | String
    },
    width: {
      type: Number | String
    },
    height: {
      type: Number | String
    }
  },
  data () {
    return {
      isDragOver : false,
      transform: {
        offsetX: 0,
        offsetY: 0
      },
    }
  },
  mounted() {
    this.initDraggable()
  },
  methods:{
    initDraggable(){
      document.addEventListener('mousedown', this.onMousedown);
    },
    offDraggable() {
      document.removeEventListener('mousedown', null);
    },
    onMousedown(e) {
      if( !this.$refs.draggable || !this.$refs.draggable.contains(e.target) ){
        return
      }
      this.isDragOver = false
      const downX = e.clientX;
      const downY = e.clientY;
      const { offsetX, offsetY } = this.transform;

      const targetRect = this.$refs.draggable.getBoundingClientRect();
      const targetLeft = targetRect.left;
      const targetTop = targetRect.top;
      const targetWidth = targetRect.width;
      const targetHeight = targetRect.height;

      const clientWidth = document.documentElement.clientWidth;
      const clientHeight = document.documentElement.clientHeight;

      const minLeft = -targetLeft + offsetX;
      const minTop = -targetTop + offsetY;
      const maxLeft = clientWidth - targetLeft - targetWidth + offsetX;
      const maxTop = clientHeight - targetTop - targetHeight + offsetY;

      const onMousemove = (e) => {
        const moveX = Math.min(
          Math.max(offsetX + e.clientX - downX, minLeft),
          maxLeft
        );
        const moveY = Math.min(
          Math.max(offsetY + e.clientY - downY, minTop),
          maxTop
        );
        this.isDragOver = Math.max(Math.abs(moveX),Math.abs(moveY))>=5

        this.transform = {
          offsetX: moveX,
          offsetY: moveY
        };
        this.$refs.draggable.style.transform = `translate(${this.addUnit(
          moveX
        )}, ${this.addUnit(moveY)})`;
      };

      const onMouseup = (e) => {
        // console.log('onMouseup' ,this.isDragOver,e)
        document.removeEventListener('mousemove', onMousemove);
        document.removeEventListener('mouseup', onMouseup);
      };

      const onMouseClick = (e) => {
        if (!this.isDragOver) {
          this.$emit('click')
        }
        e.target.removeEventListener('click', onMouseClick);
      };

      document.addEventListener('mousemove', onMousemove, { passive: true });
      document.addEventListener('mouseup', onMouseup);
      e.target.addEventListener('click', onMouseClick);
      e.preventDefault();
      e.stopPropagation();

    },
    addUnit(value, defaultUnit = 'px') {
      if (!value) return '';
      if (typeof value === 'string') {
        return value;
      } else if (typeof value === 'number') {
        return `${value}${defaultUnit}`;
      }
    },
  },
  destroyed() {
    this.offDraggable()
  },
};
</script>
<style scoped lang="scss">
.draggable-div {
  position: fixed;
  width: fit-content;
  height: fit-content;
  z-index: 10000;
  user-select: none;
}
</style>

移动端:

javascript 复制代码
<template>
  <div
      ref="floatDrag"
      class="float-position"
      :style="{
        width: Width + 'px',
        height: Height + 'px',
        left: left + 'px',
        top: top + 'px',
        right: right + 'px !important',
        zIndex: zIndex
      }"
      @touchmove.prevent
       @mousemove.prevent
  >
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "floatButton",
  data () {
    return {
      clientWidth: null,
      clientHeight: null,
      left: null,
      top: null,
      right: null,
      timer: null,
      currentTop: 0
    }
  },
  props: {
    distanceRight: {
      type: Number,
      default: 20
    },
    distanceBottom: {
      type: Number,
      default: 200
    },
    distanceTop: {
      type: Number,
      default: 20
    },
    isScrollHidden: {
      type: Boolean,
      default: false
    },
    isCanDraggable: {
      type: Boolean,
      default: true
    },
    Width: {
      type: Number,
      default: 48
    },
    Height: {
      type: Number,
      default: 48
    },
    zIndex: {
      type: Number,
      default: 50
    },
    value: {
      type: String,
      default: '悬浮按钮'
    }
  },
  mounted () {
    this.isCanDraggable &&
    this.$nextTick(() => {
      this.floatDrag = this.$refs.floatDrag;
      // 获取元素位置属性
      this.floatDragDom = this.floatDrag.getBoundingClientRect();
      // 设置初始位置
      // this.left = this.clientWidth - this.floatDragDom.width - this.distanceRight;
      this.right = this.distanceRight
      this.top = this.distanceTop
      // this.top = this.clientHeight - this.floatDragDom.height - this.distanceBottom;
      this.initDraggable();
    });
    // this.isScrollHidden && window.addEventListener('scroll', this.handleScroll);
    window.addEventListener('resize', this.handleResize);
  },
  created () {
    this.clientWidth = document.documentElement.clientWidth;
    this.clientHeight = document.documentElement.clientHeight;
  },
  BeforeDestroy () {
    window.removeEventListener('resize', this.handleResize);
  },
  methods: {
    /**
     * 窗口resize监听
     */
    handleResize () {
      this.checkDraggablePosition();
    },
    /**
     * 初始化draggable
     */
    initDraggable () {
      this.floatDrag.addEventListener('touchstart', this.toucheStart);
      this.floatDrag.addEventListener('touchmove', (e) => this.touchMove(e));
      this.floatDrag.addEventListener('touchend', this.touchEnd);
    },

    toucheStart () {
      this.canClick = false;
      this.floatDrag.style.transition = 'none';
    },
    touchMove (e) {
      this.canClick = true;
      if (e.targetTouches.length === 1) {
        // 单指拖动
        let touch = event.targetTouches[0];
        this.left = touch.clientX - this.floatDragDom.width / 2;
        this.top = touch.clientY - this.floatDragDom.height / 2;
      }
    },
    touchEnd () {
      if (!this.canClick) return; // 解决点击事件和touch事件冲突的问题
      this.floatDrag.style.transition = 'all 0.3s';
      this.checkDraggablePosition();
    },
    /**
     * 判断元素显示位置
     * 在窗口改变和move end时调用
     */
    checkDraggablePosition () {
      this.clientWidth = document.documentElement.clientWidth;
      this.clientHeight = document.documentElement.clientHeight;
      if (this.left + this.floatDragDom.width / 2 >= this.clientWidth / 2) {
        // 判断位置是往左往右滑动
        this.left = this.clientWidth - this.floatDragDom.width - 20;
      } else {
        this.left = 20;
      }
      if (this.top < 0) {
        // 判断是否超出屏幕上沿
        this.top = 20;
      }
      if (this.top + this.floatDragDom.height >= this.clientHeight) {
        // 判断是否超出屏幕下沿
        this.top = this.clientHeight - this.floatDragDom.height - 20;
      }
    }
  }
}

</script>

<style scoped lang="scss">
.float-position {
  position: fixed;
  right: 0;
  top: 60%;
  /*width: 170px;*/
  /*height: 180px;*/
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;

.content {
  border-radius: 50%;
  position: relative;
  padding: 0.8em;
  display: flex;
  align-content: center;
  justify-content: center;
}

.close {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  position: absolute;
  right: 30px;
  top: -12px;
  cursor: pointer;
}
}

.cart {
  border-radius: 50%;
  width: 5em;
  height: 5em;
  display: flex;
  align-items: center;
  justify-content: center;
}

.header-notice {
  display: inline-block;
  transition: all 0.3s;

span {
  vertical-align: initial;
}

.notice-badge {
  color: inherit;

.header-notice-icon {
  font-size: 16px;
  padding: 4px;
}
}
}

.drag-ball .drag-content {
  overflow-wrap: break-word;
  font-size: 14px;
  color: #fff;
  letter-spacing: 2px;
}

</style>
相关推荐
lichong9511 小时前
《postman、apipost、smartApi 等使用与特点 3 天路线图(可打印 PDF+互动脑图)》
前端·测试工具·macos·pdf·postman·大前端·大前端++
v***44671 小时前
PLC(电力载波通信)网络机制介绍
开发语言·网络·php
怪我冷i1 小时前
es6与es5的模块区别
前端·ecmascript·es6·ai写作
一 乐1 小时前
助农服务系统|基于SprinBoot+vue的助农服务系统(源码+数据库+文档)
前端·数据库·vue.js
by__csdn1 小时前
Vue 2 与 Vue 3:深度解析与对比
前端·javascript·vue.js·typescript·vue·css3·html5
s***35301 小时前
怎么下载安装yarn
android·前端·后端
q***64971 小时前
Spring boot整合quartz方法
java·前端·spring boot
JienDa1 小时前
JienDa聊PHP:盲盒电商实战中主流PHP框架的协同架构方略
开发语言·架构·php
行走的陀螺仪1 小时前
Vue3远程加载阿里巴巴字体图标实时更新方案
前端·icon·字体图标·阿里巴巴图标库