vue实现可拖拽移动悬浮球

news2024/12/29 9:36:01

封装悬浮球组件,文件名s-icons.vue 

<template>
  <div ref="icons" class="icons-container" :style="{ left: left + 'px', top: top + 'px' }">
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'FloatIcons',
  props: {
    // 滚动id
    scroller: {
      type: String,
      default: ''
    },
    // 距离上有下左的安全距离
    padding: {
      type: String,
      default: '10 10 10 10'
    },
    // 初始位置距离底部的距离
    bottom: {
      type: Number,
      default: 0
    }
  },
  data () {
    return {
      timer: null,
      currentTop: 0,
      clientWidth: 0,
      clientHeight: 0,
      itemWidth: 0,
      itemHeight: 0,
      left: null,
      top: null
    }
  },
  computed: {
    // 滚动对象,默认空返回window
    scrollContainer () {
      if (this.scroller === '') {
        return window
      } else {
        return document.getElementById(this.scroller)
      }
    },
    // 安全距离
    safeArea () {
      return this.padding.split(' ')
    }
  },
  created () {
    // 屏幕宽度
    this.clientWidth = document.documentElement.clientWidth
    // 屏幕高度
    this.clientHeight = document.documentElement.clientHeight
  },
  mounted () {
    this.$nextTick(() => {
      // 获取滚动元素
      this.scrollContainer.addEventListener('scroll', this.handleScrollStart)
      const div = this.$refs.icons
      // 获取宽度
      this.itemWidth = this.$refs.icons.offsetWidth
      this.itemHeight = this.$refs.icons.offsetHeight
      // 设置位置
      this.left = this.clientWidth - this.itemWidth - this.safeArea[1]
      this.top = this.clientHeight - this.itemWidth - this.bottom
      div.addEventListener('touchstart', () => {
        div.style.transition = 'none'
      })
      div.addEventListener('touchmove', e => {
        // 阻止默认动作
        e.preventDefault()
        if (e.targetTouches.length === 1) {
          const touch = event.targetTouches[0]
          this.left = touch.clientX - this.itemWidth / 2
          this.top = touch.clientY - this.itemHeight / 2
        }
      })
      div.addEventListener('touchend', () => {
        div.style.transition = 'all 0.3s'
        // 手指放开left位置
        if (this.left > this.clientWidth / 2) {
          this.left = this.clientWidth - this.itemWidth - this.safeArea[1]
        } else {
          this.left = this.safeArea[3]
        }
        // 手指放开top位置
        if (this.top < this.safeArea[0]) {
          this.top = this.safeArea[0]
        } else if (this.top > this.clientHeight - this.itemHeight - this.safeArea[2]) {
          // 不低于最低
          // this.top = this.clientHeight - this.itemHeight - this.safeArea[2]
          this.top = this.clientHeight - this.itemHeight
        }
      })
    })
  },
  beforeDestroy () {
    this.scrollContainer.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 () {
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
      if (scrollTop === this.currentTop) {
        if (this.left > this.clientWidth / 2) {
          this.left = this.clientWidth - this.itemWidth - this.safeArea[1]
        } else {
          this.left = this.safeArea[3]
        }
        clearTimeout(this.timer)
      }
    }
  }
}
</script>
<style lang="less" scoped>
.icons-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  position: fixed;
  background: #ffffff;
  box-shadow: 0px 2px 4px 0px rgba(0, 97, 209, 0.1);
  border: 1px solid rgba(21, 95, 186, 0.1);
  // border-radius: 50%;
  z-index: 1000;
  transition: all 0.3s;
}
</style>

在封装了一层组件引入s-icons.vue 文件,文件名goToHome.vue

<template>
  <float-icons class="icons-warp" :bottom="bottom" :scroller="scroller">
    <div class="float-icon-item" @click="goHomeClick">
      <div class="home-title">返回顶部</div>
    </div>
  </float-icons>
</template>
<script>
import FloatIcons from './s-icons'
export default {
  components: {
    'float-icons': FloatIcons
  },
  props: {
    // 滚动id
    scroller: {
      type: String,
      default: ''
    },
    // 初始位置距离底部的距离
    bottom: {
      type: Number,
      default: 60
    }
  },
  created () {
  },
  methods: {
    goHomeClick () {
      // 跳转其他页面
      // window.history.go(-(window.history.length - 2))
      // 回到顶部
      window.scrollTo({
        top: 0,
        left: 0,
        behavior: 'smooth' // 平滑滚动效果
      })
    }
  }
}
</script>
<style lang="less" scoped>
.icons-warp {
  border-radius: 50%;
  .float-icon-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 52px;
    height: 52px;
    .home-title {
      font-size: 14px;
      font-weight: 400;
      color: #1763bf;
    }
  }
}
</style>

最后一步,引入到页面中

<template>
  <div class="main">
    <h1>顶部</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>11111</h1>
    <h1>底部</h1>
    <GoToHome :bottom="bottom" />
  </div>
</template>

<script>
import GoToHome from './goToHome.vue'
export default {
  data () {
    return {
      bottom: 105
    }
  },
  methods: {
  },
  components: {
    GoToHome
  }
}
</script>

<style lang="less" scoped>
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1696981.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云ubuntu 24 deb安装mysql5.7问题解决

阿里云最近有了ubuntu24&#xff0c;手欠直接选了24系统来试水&#xff0c;安装mysql这里遇到麻烦了 其它问题参考ubuntu22的即可&#xff0c;以下是3个新问题&#xff1a; 阿里云ubuntu 24 deb安装mysql5.7遇到的3个问题&#xff1a; 1&#xff09;libssl1.1 (&#xff1e; …

深度神经网络——什么是决策树?

决策树 决策树是一种强大的机器学习算法&#xff0c;它通过模拟人类决策过程来解决分类和回归问题。这种算法的核心在于它如何将数据集细分&#xff0c;直至每个子集足够“纯净”&#xff0c;即包含的实例都属于同一类别或具有相似的数值范围。 开始于根节点&#xff1a;决策…

分布式事务——9种解决方案的原理与分类

目录 一、概要1. 分布式事务的概念2. 分布式事务解决方案分类 二、常见的分布式事务解决方案1. 基础的 2PC&#xff08;二阶段提交&#xff09;1.1 核心思想1.2 简介1.3 主要特点1.3.1 优点1.3.2 缺点 2. 基础的 3PC&#xff08;三阶段提交&#xff09;2.1 核心思想2.2 简介2.3…

【MySQL索引】(重点)

文章目录 一、见见索引二、认识磁盘三、索引的学习1.建立共识2.重谈page3.单page和多page同样存在效率低下的问题单page的缺陷多page的缺陷 页目录单page多page B树为什么行&#xff01;详谈细节 其他数据结构为什么不行?聚簇索引和非聚簇索引 回表查询 四、索引的操作1.创建主…

简单好用的文本识别方法--付费的好用,免费的更有性价比

文章目录 先说付费的进入真题&#xff0c;免费的来喏&#xff01;PixPin微信 先说付费的 直达网址!!! 进入真题&#xff0c;免费的来喏&#xff01; PixPin 商店里就有 使用示例&#xff1a; 可以看到&#xff1a;贴在桌面上的图片可以复制图片中的文字&#xff0c;真的很…

详细分析ping的基本知识以及常见网络故障的诊断(图文解析)

目录 前言1. 基本知识2. 常见故障分析2.1 请求超时2.2 域名无法解析 前言 由于全栈开发&#xff0c;在运维过程中难免会出现无法ping通等故障 针对多种情况进行详细分析 1. 基本知识 为了更好的加深ping的基本命令以及拓展更多知识点&#xff0c;详细科普其基本知识 ping&…

算法与数据结构汇总

刷题建议步骤 求职硬通货&#xff1a;一&#xff0c;好的学历&#xff0c;这个要下血本。本科&#xff0c;可以考研&#xff0c;读研。专科&#xff0c;可以专升本&#xff0c;再考研&#xff0c;读研&#xff0c;二&#xff0c;软考&#xff0c;一年考两次&#xff0c;有些科…

【Flutter】AppBar、TabBar和TabBarView

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Flutter学习 &#x1f320; 首发时间&#xff1a;2024年5月26日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e; 目…

到底该用英文括号还是中文括号?

这篇博客写的还挺详细的&#xff0c;不错。

最重要的时间表示,柯桥外贸俄语小班课

в第四格 1、与表示“钟点”的数词词组连用 例&#xff1a; в шесть часов утра 在早上六点 в пять тридцать 在五点半 2、与表示“星期”的名词连用 例&#xff1a; в пятницу 在周五 в следующий понедельник …

使用printf的两种方法,解决printf不能使用的问题

使用printf的两种方法&#xff0c;解决printf不能使用的问题 一、微库法 我们使用printf前要加上重定向fputc //重定义fputc函数 int fputc(int ch, FILE *f) { while((USART1->SR&0X40)0);//循环发送,直到发送完毕 USART1->DR (uint8_t) ch; return…

数字图像处理冈塞雷斯第四版课后习题答案【英文原版】

第二章 第三章 . 第四章 傅里叶变换是一个线性过程&#xff0c;而计算梯度的平方根和平方根则是非线性运算。傅里叶变换可以用来计算微分的差值(如问题4.50)&#xff0c;但必须在空间域中直接计算平方和平方根值。 (a)实际上&#xff0c;由于高通操作&#xff0c;环有一个暗中心…

LabelMe下载及关键点检测数据标注

本文关键点数据集链接,提取码:x1pk 1.LabelMe下载 这部分内容和YOLOv8_seg的标注软件是一样的,使用anaconda创建虚拟环境安装LabelMe,指令如下: conda create -n labelme python=3.6 -y conda activate labelme conda install pyqt conda install pillow pip install la…

Java进阶学习笔记23——API概述

API&#xff1a; API&#xff08;Application Programming Interface&#xff09;应用程序编程接口 就是Java帮我们写好了一些程序&#xff1a;如类、方法等等&#xff0c;我们直接拿过来用就可以解决一些问题。 为什么要学别人写好的程序&#xff1f; 不要重复造轮子。开发…

【Spring Boot】分层开发 Web 应用程序(含实例)

分层开发 Web 应用程序 1.应用程序分层开发模式&#xff1a;MVC1.1 了解 MVC 模式1.2 MVC 和三层架构的关系 2.视图技术 Thymeleaf3.使用控制器3.1 常用注解3.1.1 Controller3.1.2 RestController3.1.3 RequestMapping3.1.4 PathVariable 3.2 将 URL 映射到方法3.3 在方法中使用…

【JVM实践与应用】

JVM实践与应用 1.类加载器(加载、连接、初始化)1.1 类加载要完成的功能1.2 加载类的方式1.3 类加载器1.4 双亲委派模型1.5自定义ClassLoader1.6 破坏双亲委派模型2.1 类连接主要验证内容2.2 类连接中的解析2.3 类的初始化3.1 类的初始化时机3.2 类的初始化机制和顺序3.2 类的卸…

电子电器架构 - AUTOSAR软件架构Current Features in a Nutshell

电子电器架构 - AUTOSAR软件架构Current Features in a Nutshell 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的…

数据清洗操作及众所周知【数据分析】

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 前面的博客 数据分析—技术栈和开发环境搭建 …

7.Redis之String编码方式应用场景业务

1.内部编码 字符串类型的内部编码有 3 种&#xff1a; • int&#xff1a;8 个字节&#xff08;64位&#xff09;的⻓整型。 • embstr&#xff1a;⼩于等于 39 个字节的字符串。压缩字符串.适用于表示比较短的字符串。 • raw&#xff1a;⼤于 39 个字节的字符串。普通字…