vue模拟摇杆组件

使用vue实现模拟摇杆组件

点击中心圆拖动鼠标,最大范围是外圈圆,实时获取摇动角度

新建rocker.vue

html 复制代码
<script setup lang="ts">

const { r } = withDefaults(defineProps<{
  r: number,
  inR: number
}>(), {
  r: 100,
  inR: 30
})

let outCircleRef = ref<HTMLElement>()
let innerCircleRef = ref<HTMLElement>()
let mouseAngle = ref<number>(0)
let mouseAngleComp = computed(() => {
  return mouseAngle.value.toFixed(2) + '°'
})

defineExpose({ mouseAngleComp })

function mouseDown(e: MouseEvent) {
  document.body.addEventListener('mousemove', mouseMove)
}

function mouseMove(e: MouseEvent) {
  if (outCircleRef.value && innerCircleRef.value) {
    let centerX = outCircleRef.value.offsetLeft + r
    let centerY = outCircleRef.value.offsetTop + r
    let disToCenter = distance(centerX, centerY, e.clientX, e.clientY)
    let ratioX = 0
    let ratioY = 0
    if (disToCenter <= r) {
      ratioX = (e.clientX - outCircleRef.value.offsetLeft) / (r * 2) * 100
      ratioY = (e.clientY - outCircleRef.value.offsetTop) / (r * 2) * 100
    } else {
      let copyX = e.clientX
      let copyY = e.clientY
      while (disToCenter > r) {
        copyX += copyX > centerX ? -2 : 2
        copyY += copyY > centerY ? -2 : 2
        disToCenter = distance(centerX, centerY, copyX, copyY)
      }
      ratioX = (copyX - outCircleRef.value.offsetLeft) / (r * 2) * 100
      ratioY = (copyY - outCircleRef.value.offsetTop) / (r * 2) * 100
    }
    innerCircleRef.value.style.left = ratioX + '%'
    innerCircleRef.value.style.top = ratioY + '%'
    mouseAngle.value = (Math.atan2(ratioX, ratioY) * 180) % 360
  }
}

function mouseUp(e: MouseEvent) {
  document.body.removeEventListener('mousemove', mouseMove)
  innerCircleRef.value!.style.left = '50%'
  innerCircleRef.value!.style.top = '50%'
  mouseAngle.value = 0
}

function distance(x0: number, y0: number, x1: number, y1: number) {
  return Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
}

onMounted(() => {
  innerCircleRef.value?.addEventListener('mousedown', mouseDown)
  document.body.addEventListener('mouseup', mouseUp)
})

</script>

<template>
  <div>
    <div class="out_circle" ref="outCircleRef" :style="{ width: r * 2 + 'px', height: r * 2 + 'px' }">
      <div class="inner_circle" ref="innerCircleRef" :style="{ width: inR * 2 + 'px', height: inR * 2 + 'px' }">
        Press
      </div>
    </div>
  </div>
</template>

<style scoped>
.out_circle {
  border-radius: 50%;
  background-color: #ddd;
  position: relative;
  margin-top: 8px;

  .inner_circle {
    border-radius: 50%;
    background-color: #999;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    font-weight: bold;
    user-select: none;

    &:active {
      background: #666;
    }
  }
}
</style>

使用:

html 复制代码
<rocker ref="rockerRef" :r="100" :in-r="50"/>
相关推荐
夏幻灵5 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
Lvan的前端笔记5 小时前
docker:每个前端项目一个 Nginx 容器还是只有一个Nginx容器
前端·nginx·docker
三天不学习6 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
xcs194056 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
明月_清风7 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
Csvn8 小时前
框架性能优化
前端
雪芽蓝域zzs10 小时前
第 5 节:Marker 点击弹出 InfoWindow 信息窗口
vue.js
回眸&啤酒鸭10 小时前
【回眸】OpenSwarm 多智能体协作系统实战指南
大数据·前端·人工智能
用户693717500138410 小时前
2026,程序员的时代拐点到了
android·前端·后端
大龄秃头程序员10 小时前
一次 iBeacon + BLE 无感解锁方案的实现记录
前端