悬浮工具球(仿 iphone 辅助触控)

news2024/11/14 20:17:00

悬浮工具球(仿 iphone 辅助触控)

  • 兼容移动端 touch 事件
  • 点击元素以外位置收起
  • 解决鼠标抬起触发元素的点击事件问题

Demo

Github

<template>
  <div
    ref="FloatingBal"
    class="floating_ball"
    :class="[dragging, isClick]"
    :style="dragStatus ? computedStyle : ''"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
    @mousedown="onButtonDown"
    @touchstart="onButtonDown"
    @focus="handleMouseEnter"
    @blur="handleMouseLeave"
  >
    <div
      class="floating_ball_inner"
      :class="[{ large }]"
      @click="handleBallClick"
      v-click-outside="handleClickOutside"
    >
      <div
        class="fbi_ring"
        v-show="!large"
      >
      </div>
      <div
        class="fbi_nav"
        v-show="large"
      >
        <div
          v-for="(item, index) of 9"
          class="fn_item"
          :key="index"
        >
          {{ index + 1 }}
        </div>
      </div>
    </div>
  </div>
</template>

<script>
// 创建一个全局的点击事件处理函数
const handleClickOutside = (event, el, binding) => {
  // 检查点击的元素是否在绑定的元素内部
  if (!(el === event.target || el.contains(event.target))) {
    // 如果点击的元素不在绑定的元素内部,则触发绑定的回调函数
    binding.value()
  }
}
export default {
  name: 'FloatingBallVue',

  directives: {
    clickOutside: {
      bind: function (el, binding) {
        // 创建一个点击事件处理函数,并将它保存在元素的属性中
        const handleClick = event => handleClickOutside(event, el, binding)
        el.__vueClickOutside__ = handleClick

        // 在 document 上监听点击事件
        document.addEventListener('click', handleClick)
      },
      // 指令的解绑函数,在元素从 DOM 中移除时调用
      unbind(el) {
        // 移除之前保存在元素属性中的点击事件处理函数
        document.removeEventListener('click', el.__vueClickOutside__)
        delete el.__vueClickOutside__
      }
    }
  },

  components: {},

  props: {
    name: {
      type: String,
      default: ''
    },
    obj: {
      type: Object,
      default() {
        return {}
      }
    }
  },

  data() {
    return {
      large: false,
      newPosition: {
        left: 0,
        top: 0
      },
      startX: 0,
      startY: 0,
      currentX: 0,
      currentY: 0,
      disX: 0,
      disY: 0,
      grid: false,
      dragStatus: false,
      isClick: false,
      dragging: false,
      hovering: false
    }
  },

  computed: {
    computedStyle() {
      return {
        left: this.newPosition.left + 'px',
        top: this.newPosition.top + 'px',
        right: 'auto',
        bottom: 'auto'
      }
    }
  },

  watch: {},

  mounted() {
    window.addEventListener(
      'touchmove',
      function (event) {
        event.preventDefault()
      },
      { passive: false }
    )
  },

  methods: {
    handleClickOutside() {
      this.large = false
    },
    handleBallClick() {
      if (this.dragging && this.isClick) {
        this.large = !this.large
      }
    },
    setPosition() {
      this.newPosition.left = this.currentX - this.disX
      this.newPosition.top = this.currentY - this.disY
    },
    onDragging(event) {
      if (event.type === 'touchmove') {
        event.clientY = event.touches[0].clientY
        event.clientX = event.touches[0].clientX
      }
      this.currentY = event.clientY
      this.currentX = event.clientX
      const disX = this.currentX - this.startX
      const disY = this.currentY - this.startY
      if (Math.abs(disX) < 5 && Math.abs(disY) < 5) {
        // 未移动
      } else {
        this.dragStatus = true
        if (this.dragging) {
          this.isClick = false
          this.setPosition()
        }
      }
    },
    onDragEnd() {
      if (this.dragging) {
        /*
         * 防止在 mouseup 后立即触发 click,导致滑块有几率产生一小段位移
         * 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
         */
        setTimeout(() => {
          this.dragging = false
          if (!this.isClick) {
            this.setPosition()
          }
        }, 0)
        window.removeEventListener('mousemove', this.onDragging)
        window.removeEventListener('touchmove', this.onDragging)
        window.removeEventListener('mouseup', this.onDragEnd)
        window.removeEventListener('touchend', this.onDragEnd)
        window.removeEventListener('contextmenu', this.onDragEnd)
      }
    },
    onDragStart(event) {
      this.dragging = true
      this.isClick = true
      if (event.type === 'touchstart') {
        event.clientY = event.touches[0].clientY
        event.clientX = event.touches[0].clientX
      }
      this.startY = event.clientY
      this.startX = event.clientX
      this.disX = this.startX - this.$refs.FloatingBal.offsetLeft
      this.disY = this.startY - this.$refs.FloatingBal.offsetTop
    },
    onButtonDown(event) {
      if (event.type === 'touchstart') {
        event.stopPropagation()
      } else {
        event.stopPropagation()
        event.preventDefault()
      }
      this.onDragStart(event)
      window.addEventListener('mousemove', this.onDragging)
      window.addEventListener('touchmove', this.onDragging)
      window.addEventListener('mouseup', this.onDragEnd)
      window.addEventListener('touchend', this.onDragEnd)
      window.addEventListener('contextmenu', this.onDragEnd)
    },
    handleMouseLeave() {
      this.hovering = false
    },
    handleMouseEnter() {
      this.hovering = true
    }
  }
}
</script>

<style lang='scss' scoped>
.floating_ball {
  position: absolute;
  z-index: 9;
  top: 160px;
  right: 80px;
  cursor: pointer;
  transform: translateX(-50%) translateY(-50%);
  .floating_ball_inner {
    width: 80px;
    height: 80px;
    transition: all 0.2s;
    border-radius: 12px;
    background-color: rgba($color: #333333, $alpha: 0.4);
    .fbi_ring {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 60%;
      height: 60%;
      transform: translateX(-50%) translateY(-50%);
      border-radius: 50%;
      background-color: rgba($color: #ffffff, $alpha: 0.3);
      &::before,
      &::after {
        position: absolute;
        top: 50%;
        left: 50%;
        content: '';
        transform: translateX(-50%) translateY(-50%);
        border-radius: 50%;
      }
      &::before {
        width: 80%;
        height: 80%;
        background-color: rgba($color: #ffffff, $alpha: 0.4);
      }
      &::after {
        width: 60%;
        height: 60%;
        background-color: rgba($color: #ffffff, $alpha: 0.5);
      }
    }
    .fbi_nav {
      display: flex;
      flex-wrap: wrap;
      width: 100%;
      height: 100%;
      .fn_item {
        display: flex;
        align-items: center;
        flex-flow: column;
        justify-content: center;
        width: 33.3%;
        height: 33.3%;
      }
    }
    &.large {
      width: 240px;
      height: 240px;
    }
  }
}
</style>

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

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

相关文章

【Docker4】使用Harbor搭建私有仓库

Docker私有仓库一、搭建本地私有仓库1、daemon.json 配置文件中常用配置项2、搭建私有仓库3、Docker容器重启策略 二、Docker--harbor私有仓库部署与管理1、Harbor 简介2、Harbor的特性3、Harbor的构成4、Harbor 部署4.1、部署 Docker-Compose 服务4.2、部署 Harbor 服务4.3、启…

OJ_判断两序列是否为同一二叉搜索树序列

题干 C实现 求出两棵树的中序和先序序列对比中序和先序序列是否相同 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string> #include <queue> using namespace std;struct TreeNode {char data;TreeNode* left;TreeNode* right; };void …

day17_订单(结算,提交订单,支付页,立即购买,我的订单)

文章目录 订单模块1 结算1.1 需求说明1.2 获取用户地址1.2.1 UserAddress1.2.2 UserAddressController1.2.3 UserAddressService1.2.4 UserAddressMapper1.2.5 UserAddressMapper.xml 1.3 获取购物项数据1.3.1 CartController1.3.2 CartService1.3.3 openFeign接口定义 1.4 环境…

VUE_nuxt启动只能通过localhost访问,ip访问不到:问题解决

修改项目根目录下的 package.json "config": {"nuxt": {"host": "0.0.0.0","port": "3000"} } 这样项目启动后就可以通过ip进行访问了

ViewModel

1.ViewModel的诞生与作用 Activity package com.tiger.viewmodel;import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider;import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Te…

Codeforces Round 929 (Div. 3)- ABCDEFG

A:Turtle Puzzle: Rearrange and Negate 思路&#xff1a; 将负的元素全部排到一起&#xff0c;然后对它们符号取反&#xff0c;然后求所有元素的和&#xff0c;此时就是最大值了。 代码&#xff1a; #include<iostream> using namespace std;void solve() {int n;cin&…

电脑资料管理软件(5个高效批量管理电脑资料的方法)

企业电脑资料管理是企业一大难题&#xff0c;为什么这样说&#xff1f; 首先&#xff0c;企业电脑资料的数量庞大且种类繁多。 其次&#xff0c;电脑资料的安全性和保密性要求高。 再者&#xff0c;电脑资料的管理涉及到多个部门和员工的协作。 ...... 针对此类情况很多企业…

Vue中的组件:构建现代Web应用的基石

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

基于redis实现互斥锁

利用setnx命令实现类似获取锁和释放锁。 获取锁&#xff0c;setnx lock 1&#xff0c;返回值为1视为获取成功&#xff0c;为0视为获取失败 释放锁&#xff0c;del lock 特殊情况&#xff1a; 如果获取锁之后&#xff0c;锁来还来不及释放&#xff0c;redis宕机了&#xff0c;这…

MIT6.5840(6.824)Lab2总结(Raft)

MIT6.5840&#xff08;原MIT6.824&#xff09;Lab2总结&#xff08;Raft&#xff09; 资源分享&#xff1a; 官网地址&#xff1a;http://nil.csail.mit.edu/6.5840/2023/ Raft论文地址&#xff1a;http://nil.csail.mit.edu/6.5840/2023/papers/raft-extended.pdf 官方学生…

gumbel-softmax如何实现离散分布可微+torch代码+原理+证明

文章目录 背景方法通俗理解什么是重参数化gumbel-softmax为什么是gumbeltorch实现思考 背景 这里举一个简单的情况&#xff0c;当前我们有p1, p2, p3三个概率&#xff0c;我们需要得到最优的一个即max(p1, p2, p3)&#xff0c;例如当前p3 max(p1, p2, p3)&#xff0c;那么理想…

SSRF漏洞基础原理(浅层面解释 + 靶场演示)

一、SSRF漏洞的基本概念&#xff1a; SSRF--全名&#xff1a;Server-Side Request Forgery&#xff0c;汉译&#xff1a;服务端请求伪造&#xff0c;漏洞别名“借刀杀人”。 想象以下&#xff0c;现存在一个 Web应用&#xff0c;这个Web应用可以帮助我们能爬取互联网上的其他…

js 添加、删除DOM元素

1. js添加、删除DOM元素 1.1. 添加DOM元素 1.1.1. appendChild()方法 该方法添加的元素位于父元素的末尾&#xff0c;使用方法&#xff1a; parentNode.appenChild(NewNode) // parentNode是需要添加元素的容器&#xff0c;NewNode是新添加的元素   创建一个li元素并添加到…

数据结构与算法-哈希表

引言 在计算机科学中&#xff0c;数据结构与算法是构建高效软件系统的关键基石。其中&#xff0c;哈希表作为一种非常实用的数据结构&#xff0c;以其快速查找、插入和删除等特性&#xff0c;在诸多领域发挥着无可替代的作用。本文将深入探讨哈希表的工作原理、实现细节以及其在…

Qt Creator常见问题解决方法

Qt Creator源文件重命名的正确方法 光改文件名是不够的&#xff0c;还要在.pro文件中的SOURCES中把名字改成之后的。 中文乱码&#xff08;字符集设置&#xff09; 菜单栏-工具-选项-设置为utf-8

Reset Verification IP

Reset Verification IP IP 参数及接口 IP 例化界面 相关函数 assert_reset //置位复位信号 < hierarchy_path>.assert_reset();deassert_reset //取消置位复位信号 < hierarchy_path>.deassert_reset();set_master_mode //设置 RST_VIP 模式为 Master < hi…

STM32基本定时功能

1、定时器就是计数器。 2、怎么计数&#xff1f; 3、我们需要有一恒定频率的方波信号&#xff0c;再加上一个寄存器。 4、比如每来一个上升沿信号&#xff0c;寄存器值加1&#xff0c;就可以完成计数。 5、假设方波频率是100Hz&#xff0c;也就是1秒100个脉冲。…

严刑拷打_微服务

文章详情 &#xff1a;&#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a; 微服务相关知识 ⏱️ 创作时间&#xff1a;2024年03月8日 ———————————————— 文章目…

[数据结构]队列

1.队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出 FIFO(First In First Out) 入队列&#xff1a;进行插入操作的一端称为队尾 出队列&#xff1a;进行删除操作的一端称为队头 2…

Dynamo3.0.3——六年来最大的更新

Hello大家好&#xff01;我是九哥~ 前几天&#xff0c;Dynamo Core 3.0.0版本发布&#xff0c;迎来了Dynamo六年来最大的一次更新。最大的改变&#xff0c;是更新到了.net8&#xff0c;这回对Dynamo节点包产生不小影响。接下来我们详细看一下都有哪些变化。 首先&#xff0…