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

news/2024/7/20 20:56:18 标签: iphone, ios, vue.js, 前端, javascript

iphone__0">悬浮工具球(仿 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.niftyadmin.cn/n/5419382.html

相关文章

arduino uno R3驱动直流减速电机(蓝牙控制)

此篇博客用于记录使用arduino驱动直流减速电机的过程&#xff0c;仅实现简单的功能&#xff1a;PID调速、蓝牙控制 1、直流减速电机简介2、DRV8833电机驱动模块简介3、HC-05蓝牙模块简介电机转动测试4、PID控制5、蓝牙控制电机 1、直流减速电机简介 我在淘宝购买的电机&#x…

2024.3.10周报

目录 摘要 ABSTRACT 一、文献阅读 1. 题目 2. 连续时间模型 3. 离散时间模型 4.结论 二、CLSTM 1. 任务要求 2. 实验结果 3. 实验代码 3.1模型构建 3.2训练过程代码 小结 摘要 本文主要讨论PINN。本文简要介绍了Lipschitz条件。其次本文展示了题为Physics-infor…

Python接口自动化测试:断言封装详解

前言 在进行API接口测试时&#xff0c;断言起着至关重要的作用。断言是用于验证预期结果与实际结果是否一致的过程。在Python中&#xff0c;我们可以利用一些库来实现断言功能。 1. 安装必要的库 在Python中&#xff0c;我们主要会使用两个库&#xff1a;requests和jsonpath…

PromptBreeder---针对特定领域演化和发展提示词的方法

原文地址&#xff1a;promptbreeder-evolves-adapts-prompts-for-a-given-domain 论文地址&#xff1a;https://arxiv.org/pdf/2309.16797.pdf 2023 年 10 月 6 日 提示方法分为两大类 硬提示是由人工精心设计的文本提示&#xff0c;包含离散的输入令牌&#xff1b;其缺点…

docker安装jenkins并实现CICD流程

docker安装jenkins并实现CICD流程 本文目录 docker安装jenkins并实现CICD流程安装命令初始化设置更新jenkins及插件更新jenkins版本更新插件 创建第一个任务修改配置插件更新中心时区设置 安装命令 官方安装参考&#xff1a;https://www.jenkins.io/zh/doc/book/installing/ …

OpenFeign的常规使用

架构: 一.新建module 引入依赖: <!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency> yml配置; server:port: 80spring:applicati…

HTTP常见报错响应码

HTTP 响应状态代码指示特定 HTTP 请求是否已成功完成。响应分为五类&#xff1a; 信息响应成功响应重定向客户端错误服务器错误 1 、信息响应 100 Continue 这个临时响应表明&#xff0c;迄今为止的所有内容都是可行的&#xff0c;客户端应该继续请求&#xff0c;如果已经完 …

十三、类的继承、访问级别

类的继承与访问控制 base关键字 base关键字的用法&#xff0c;有些类似Java中的super关键字 调用基类的构造函数&#xff1a;当创建派生类的实例时&#xff0c;可以使用 base 关键字来调用基类的构造函数。 这个用法很重要下面有详细解释&#xff0c;初学到这里&#xff0c;弄懂…