JS前端实现摇杆轮盘

前端用JS实现一个向手游中可以360度滑动方向的控制轮盘。

这是从我的项目中抽出来的代码DEMO:

注: 因为是用在3D中,所以在屏幕中y轴的滑动对应的是3D世界中的z轴。所以代码中会有y,z,要注意区分。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    html,body{
      margin: 0;
      background-color: #1a2d42;
      position: relative;
      height: 100%;
    }
    .wheel{
      height: 100px;
      width: 100px;
      position: absolute;
      left: 30px;
      bottom: 30px;
      border-radius: 50%;
      background-image: radial-gradient(rgba(255, 255, 255, 0.2) 40%, rgba(255, 255, 255, 0.3) 40%);
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #fff;
    }
    .wheel .tap{
      width: 30px;
      height: 30px;
      border-radius: 50%;
      background-color: rgba(255, 255, 255, 0.5);
    }
  </style>
</head>
<body>
  <div class="wheel">
    <div class="tap"></div>
  </div>

<script>
const wheelRef = document.querySelector('.wheel')
const tapRef = document.querySelector('.tap')
function event() {
  wheelRef.addEventListener('touchstart', (event) => {
    event.stopPropagation()
    const { clientX, clientY } = event.targetTouches[0]
    setTapPosition(clientX, clientY)
    emit('start', true)
  })
  wheelRef.addEventListener('touchend', () => {
    tapRef.style.transform = `translate(0px, 0px)`
    emit('change', {x: 0, z: 0})
    emit('stop', true)
  })
  wheelRef.addEventListener('touchmove', (event) => {
    event.stopPropagation()
    const { clientX, clientY } = event.targetTouches[0]
    setTapPosition(clientX, clientY)
  })
}

function setTapPosition(clientX, clientY) {
  tapRef.style.transform = `translate(0px, 0px)`
  const { width, left, top } = tapRef.getBoundingClientRect()
  let x = clientX - (left + width * 0.5)
  let y = clientY - (top +  width * 0.5)
  let z = Math.sqrt(x * x + y * y)
  // 超出轮盘
  const MaxR = 50
  if (z > MaxR) {
    let outX = clientX - left - width * 0.5
    let outY = clientY - top - width * 0.5
    const outZ = Math.sqrt(Math.pow(outX, 2) + Math.pow(outY, 2))
    const rate = outZ / MaxR
    x = outX / rate
    y = outY / rate
  }
  tapRef.style.transform = `translate(${x}px, ${y}px)`
  emit('change', {x: x / MaxR, z: y / MaxR})
}

let moving = false
function change(x, z) {
  tapRef.style.transform = `translate(0px, 0px)`
  emit('change', {x, z})
  if (x === 0 && z === 0) {
    emit('stop', true)
    moving = false
  }
  else {
    if (!moving) {
      moving = true
      emit('start', true)
    }
  }
  tapRef.style.transform = `translate(${x * 30}px, ${z * 30}px)`
} 

function emit(type, rs) {
  console.log(type, rs)
}

event()
</script>
</body>
</html>

css和html部分就跳过了。

滑动范围在轮盘内时,很好处理。记录手触摸时的位置,与手滑动时的偏移相减就行了。

第一个方法event(),就是用于监听手指touch操作的。

第二个方法setTapPosition(),根据手指touch事件的位置对按钮进行位移。

第三个change()方法是控制人物模型动作的,比如停止,走动。这里可以不看。

最后一个方法emit是上报动作和移动方向的。

需要特别处理的是,当手滑出轮盘时,如何保证按钮不飞出轮盘,且能丝滑移动。

最初我的想法是如果按钮的偏移大于轮盘半径,则停止偏移。然而如此粗爆的处理会使按钮卡住。改良后的主要代码就是:

  // 超出轮盘
  const MaxR = 50
  if (z > MaxR) {
    let outX = clientX - left - width * 0.5
    let outY = clientY - top - width * 0.5
    const outZ = Math.sqrt(Math.pow(outX, 2) + Math.pow(outY, 2))
    const rate = outZ / MaxR
    x = outX / rate
    y = outY / rate
  }

图形示意大至就是:

以中心点为起点,按钮偏移的距离是z,手指滑出的距离是outZ,手指所在的x,y轴距离为outX, outY

如果z大于轮盘半径MaxR,就是滑出了轮盘。这时需要开始处理

这时应该是按钮在轮盘的边缘上而不飞出。所以按钮的最大偏移距离应该是marR

根据轮盘半径MaxR与outZ的比值,可以计算出此时对应的x, y值,也就是按钮的x,y坐标了。就可以使按钮保持在轮盘内了,而且仍可以跟随手指的滑动改变位置方向,很丝滑。

2023-03-10 15:01:31 696 0

参与讨论

选择你的头像