Files
wrobot/Shibo-main-V4-ESPNOW/test/basic_web.html
T

442 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>控制器</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;background-color:#fff;font-family:'Segoe UI',Arial,sans-serif;padding:20px}
.page-title{color:#333;margin-bottom:40px;text-align:center;font-weight:600}
.joysticks-container{display:flex;gap:60px;align-items:center;justify-content:center;flex-wrap:wrap;width:100%;max-width:1200px}
.joystick-item{display:flex;flex-direction:column;align-items:center;gap:15px}
.joystick-label{color:#333;font-weight:500;font-size:16px;text-align:center;padding:5px 10px;border-radius:4px;background-color:rgba(255,255,255,.8)}
.joystick-wrapper{display:flex;position:relative}
.joystick-wrapper.vertical{flex-direction:column;align-items:center}
.joystick-wrapper.horizontal{flex-direction:row;align-items:center}
.joystick-track{position:relative;box-shadow:0 4px 12px rgba(0,0,0,.1);background-color:#e0e0e0;border-radius:30px;overflow:hidden;transition:box-shadow .2s ease}
.joystick-track:hover{box-shadow:0 6px 16px rgba(0,0,0,.15)}
.joystick-track.vertical{width:60px;height:300px}
.joystick-track.horizontal{width:300px;height:60px}
.joystick-handle{position:absolute;width:50px;height:50px;background-color:#4a90e2;border-radius:50%;box-shadow:0 3px 8px rgba(0,0,0,.25);cursor:grab;transition:transform .1s ease-out,background-color .2s ease;user-select:none;-webkit-user-select:none;border:2px solid rgba(255,255,255,.7)}
.joystick-handle:active{cursor:grabbing;background-color:#357abd;transform:scale(.95)}
.joystick-wrapper.vertical .joystick-handle{left:5px;top:125px}
.joystick-wrapper.horizontal .joystick-handle{left:125px;top:5px}
.value-display{display:flex;align-items:center;justify-content:center;background-color:#e0e0e0;color:#fff;border-radius:8px;font-weight:600;font-size:18px;width:60px;height:40px;box-shadow:0 2px 5px rgba(0,0,0,.1);transition:background-color .2s ease}
.value-display.active{background-color:#4a90e2}
.joystick-wrapper.vertical .value-display{margin-top:20px}
.joystick-wrapper.horizontal .value-display{margin-left:20px}
.usage-info{margin-top:40px;color:#666;font-size:14px;text-align:center;max-width:600px;line-height:1.5}
@media (max-width:1024px){.joysticks-container{gap:40px}
.joystick-track.vertical{width:50px;height:250px}
.joystick-track.horizontal{width:250px;height:50px}
.joystick-handle{width:40px;height:40px}
.joystick-wrapper.vertical .joystick-handle{left:5px;top:105px}
.joystick-wrapper.horizontal .joystick-handle{left:105px;top:5px}
}
@media (max-width:768px){.joysticks-container{flex-direction:column;gap:60px}}
.button-container {
display: flex;
gap: 50px;
margin-bottom: 20px;
}
.control-btn {
padding: 12px 24px;
background: #fff;
color: #2196F3;
border: 2px solid #2196F3;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
}
</style>
</head>
<body onload="javascript:socket_init()">
<h1 class="page-title">控制器</h1>
<div class="button-container">
<button class="control-btn" id="blink">blink</button>
<button class="control-btn" id="dir">jump</button>
</div>
<div class="joysticks-container">
<!-- 1. 水平摇杆 -->
<div class="joystick-item">
<div class="joystick-label">摇摆</div>
<div id="roll" class="joystick-wrapper horizontal"></div>
</div>
<!-- 2. 垂直摇杆 -->
<div class="joystick-item">
<div class="joystick-label">高度</div>
<div id="height" class="joystick-wrapper vertical"></div>
</div>
<!-- 3. 垂直摇杆 -->
<div class="joystick-item">
<div class="joystick-label">前后</div>
<div id="joy_y" class="joystick-wrapper vertical"></div>
</div>
<!-- 4. 水平摇杆 -->
<div class="joystick-item">
<div class="joystick-label">左右</div>
<div id="joy_x" class="joystick-wrapper horizontal"></div>
</div>
</div>
<script>
var socket; // socket通信
function socket_init() {
socket = new WebSocket('ws://' + window.location.hostname + ':81/'); // sta模式
}
// 摇杆控制器类 - 封装所有功能
class Joystick {
/**
* 创建一个新的摇杆实例
* @param {string} containerId - 容器元素的ID
* @param {string} direction - 方向,可选值:'vertical' 或 'horizontal'
*/
constructor(containerId, direction = 'vertical', min = -100, max = 100) {
// 初始化属性
this.container = document.getElementById(containerId);
this.direction = direction;
this.id = containerId;
this.isDragging = false;
this.startPos = 0;
this.currentPos = 0;
this.centerPos = 0;
this.min = min;
this.max = max;
this.range = max - min;
// 创建摇杆元素
this.createElements();
// 获取元素引用
this.handle = this.container.querySelector('.joystick-handle');
this.track = this.container.querySelector('.joystick-track');
this.valueDisplay = this.container.querySelector('.value-display');
// 初始化尺寸和位置
this.initDimensions();
// 绑定事件处理函数
this.bindEvents();
// 设置初始位置
this.setHandlePosition(this.centerPos);
}
/**
* 创建摇杆所需的DOM元素
*/
createElements() {
// 创建轨道元素
const track = document.createElement('div');
track.className = `joystick-track ${this.direction}`;
// 创建把手元素
const handle = document.createElement('div');
handle.className = 'joystick-handle';
// 创建数值显示元素
const valueDisplay = document.createElement('div');
valueDisplay.className = 'value-display';
valueDisplay.textContent = '0';
// 组合元素
track.appendChild(handle);
this.container.appendChild(track);
this.container.appendChild(valueDisplay);
}
/**
* 初始化摇杆尺寸和位置参数
*/
initDimensions() {
if (this.direction === 'vertical') {
// 纵向摇杆尺寸
this.trackSize = this.track.offsetHeight;
this.handleSize = this.handle.offsetHeight;
this.centerPos = (this.trackSize - this.handleSize) / 2;
} else {
// 横向摇杆尺寸
this.trackSize = this.track.offsetWidth;
this.handleSize = this.handle.offsetWidth;
this.centerPos = (this.trackSize - this.handleSize) / 2;
}
}
/**
* 限制值在指定范围内
* @param {number} value - 要限制的值
* @param {number} min - 最小值
* @param {number} max - 最大值
* @returns {number} 限制后的值
*/
clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
/**
* 更新显示的数值
* @param {number} position - 把手的位置
*/
updateValue(position) {
// 计算原始值(-100~100)
const offset = this.direction === 'vertical'
? this.centerPos - position
: position - this.centerPos;
const rawPercentage = (offset / this.centerPos) * 100;
const rawValue = this.clamp(Math.round(rawPercentage), -100, 100);
// 计算映射值
const mappedValue = Math.round(
((rawValue + 100) / 200) * this.range + this.min
);
// 更新显示映射值
this.valueDisplay.textContent = mappedValue;
// 触发事件携带双重值
this.dispatchValueChange({
raw: rawValue,
mapped: mappedValue,
min: this.min,
max: this.max
});
// 活动状态样式
if (mappedValue !== this.min && mappedValue !== this.max) {
this.valueDisplay.classList.add('active');
} else {
this.valueDisplay.classList.remove('active');
}
}
/**
* 触发值变化事件
* @param {number} value - 当前值
*/
dispatchValueChange({raw, mapped}) {
const event = new CustomEvent('valuechange', {
detail: {
rawValue: raw,
mappedValue: mapped,
min: this.min,
max: this.max,
id: this.id,
direction: this.direction,
joystick: this
},
bubbles: true
});
this.container.dispatchEvent(event);
}
/**
* 设置摇杆把手位置
* @param {number} pos - 位置值(纵向是top,横向是left)
*/
setHandlePosition(pos) {
// 限制在轨道范围内
const minPos = 0;
const maxPos = this.trackSize - this.handleSize;
const constrainedPos = this.clamp(pos, minPos, maxPos);
// 设置位置
if (this.direction === 'vertical') {
this.handle.style.top = `${constrainedPos}px`;
} else {
this.handle.style.left = `${constrainedPos}px`;
}
// 更新数值
this.updateValue(constrainedPos);
}
/**
* 回到中心位置(带动画效果)
*/
returnToCenter() {
if (!this.isDragging) {
const currentPosition = parseFloat(
this.direction === 'vertical'
? this.handle.style.top
: this.handle.style.left
) || this.centerPos;
const distance = this.centerPos - currentPosition;
// 如果距离很小,直接回到中心
if (Math.abs(distance) < 1) {
this.setHandlePosition(this.centerPos);
return;
}
// 平滑动画回到中心
const step = distance * 0.5; // 每次移动剩余距离的20%
this.setHandlePosition(currentPosition + step);
// 继续动画
requestAnimationFrame(() => this.returnToCenter());
}
}
/**
* 绑定事件处理函数
*/
bindEvents() {
// 鼠标按下事件
this.handle.addEventListener('mousedown', (e) => {
this.isDragging = true;
this.startPos = this.direction === 'vertical' ? e.clientY : e.clientX;
this.currentPos = parseFloat(
this.direction === 'vertical'
? this.handle.style.top
: this.handle.style.left
) || this.centerPos;
e.preventDefault();
});
// 鼠标移动事件
document.addEventListener('mousemove', (e) => {
if (this.isDragging) {
const delta = (this.direction === 'vertical' ? e.clientY : e.clientX) - this.startPos;
this.setHandlePosition(this.currentPos + delta);
}
});
// 鼠标释放事件
document.addEventListener('mouseup', () => {
if (this.isDragging) {
this.isDragging = false;
this.returnToCenter();
}
});
// 触摸屏开始事件
this.handle.addEventListener('touchstart', (e) => {
this.isDragging = true;
this.startPos = this.direction === 'vertical'
? e.touches[0].clientY
: e.touches[0].clientX;
this.currentPos = parseFloat(
this.direction === 'vertical'
? this.handle.style.top
: this.handle.style.left
) || this.centerPos;
e.preventDefault(); // 防止触摸事件被浏览器默认处理
});
// 触摸屏移动事件 - 修改事件绑定方式,添加passive选项
document.addEventListener('touchmove', (e) => {
if (this.isDragging) {
const delta = (this.direction === 'vertical'
? e.touches[0].clientY
: e.touches[0].clientX) - this.startPos;
this.setHandlePosition(this.currentPos + delta);
e.preventDefault(); // 防止页面滚动
}
}, { passive: false }); // 关键修改:明确指定passive为false
// 触摸屏结束事件
document.addEventListener('touchend', () => {
if (this.isDragging) {
this.isDragging = false;
this.returnToCenter();
}
});
// 窗口大小变化时重新计算尺寸
window.addEventListener('resize', () => {
this.initDimensions();
if (!this.isDragging) {
this.setHandlePosition(this.centerPos);
}
});
}
}
const controlState = {
blink: 0,
dir: "stop", // 4 = stop, 5 = jump
roll: 0,
height: 30,
joy_y: 0,
joy_x: 0
};
// 统一发送函数
function sendAllData() {
const data = {
dir: controlState.dir,
roll: controlState.roll,
height: controlState.height,
joy_y: controlState.joy_y,
joy_x: controlState.joy_x,
blink: controlState.blink,
stable: 1,
'mode': 'basic',
};
console.log(data);
// socket.send(JSON.stringify(data));
// 重置瞬时状态
controlState.blink = 0;
controlState.dir = "stop";
}
// 初始化摇杆实例
document.addEventListener('DOMContentLoaded', () => {
// 创建四个摇杆,按顺序:水平、垂直、垂直、水平
const joysticks = [
new Joystick('roll', 'horizontal', -30, 30),
new Joystick('height', 'vertical', 0, 60),
new Joystick('joy_y', 'vertical'),
new Joystick('joy_x', 'horizontal')
];
// 监听所有摇杆的数值变化事件
document.querySelectorAll('.joystick-wrapper').forEach((wrapper, id) => {
wrapper.addEventListener('valuechange', (e) => {
// console.log(`摇杆 ${e.detail.id} (${e.detail.direction}) 值: ${e.detail.value}`);
// console.log(e.detail);
switch(e.detail.id) {
case 'roll':
controlState.roll = e.detail.mappedValue;
break;
case 'height':
controlState.height = e.detail.mappedValue;
break;
case 'joy_y':
controlState.joy_y = e.detail.mappedValue;
break;
case 'joy_x':
controlState.joy_x = e.detail.mappedValue;
break;
}
sendAllData();
});
});
document.querySelectorAll('.control-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
switch(e.target.id) {
case 'blink':
controlState.blink = 1;
break;
case 'dir':
controlState.dir = "jump";
break;
}
sendAllData();
});
});
});
</script>
</body>
</html>