前端用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坐标了。就可以使按钮保持在轮盘内了,而且仍可以跟随手指的滑动改变位置方向,很丝滑。