八方位判斷滑動方向,用判斷用户鼠標或者觸摸屏方位和滑動速度。
實用場景為:平滑滾動時候判斷方位及漂移位置。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>滑動速度&方位檢測</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
overflow-x: hidden;
}
.container {
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
width: 90%;
max-width: 800px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
text-align: center;
margin-bottom: 30px;
}
h1 {
margin-top: 0;
font-weight: 600;
margin-bottom: 10px;
}
.subtitle {
opacity: 0.9;
margin-bottom: 20px;
}
.touch-area {
width: 100%;
height: 300px;
background-color: rgba(255, 255, 255, 0.15);
border-radius: 15px;
margin: 20px 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
cursor: pointer;
transition: background-color 0.3s;
user-select: none;
touch-action: none;
position: relative;
overflow: hidden;
}
.touch-area.active {
background-color: rgba(255, 255, 255, 0.25);
}
.touch-trail {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.7);
pointer-events: none;
transform: translate(-50%, -50%);
}
.stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-top: 20px;
}
.stat-box {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 15px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
margin: 10px 0;
}
.unit {
font-size: 14px;
opacity: 0.8;
}
.direction-container {
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 0;
}
.direction-indicator {
width: 200px;
height: 200px;
position: relative;
margin: 20px auto;
}
.direction-arrow {
position: absolute;
font-size: 30px;
opacity: 0.3;
transition: all 0.3s;
transform-origin: center;
}
.direction-arrow.active {
opacity: 1;
transform: scale(1.3);
text-shadow: 0 0 10px rgba(255, 255, 255, 0.7);
}
.direction-name {
font-size: 20px;
font-weight: bold;
margin-top: 10px;
min-height: 30px;
}
.instructions {
margin-top: 20px;
font-size: 14px;
opacity: 0.8;
line-height: 1.5;
}
.history {
margin-top: 20px;
text-align: left;
max-height: 150px;
overflow-y: auto;
padding: 10px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 10px;
}
.history-item {
padding: 5px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.debug-info {
margin-top: 10px;
font-size: 12px;
opacity: 0.7;
}
@media (max-width: 600px) {
.stats {
grid-template-columns: 1fr;
}
.direction-indicator {
width: 150px;
height: 150px;
}
.direction-arrow {
font-size: 24px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>滑動速度方位&檢測</h1>
<p class="subtitle">在下方區域滑動手指,查看滑動速度、角度和8個方位</p>
<div class="touch-area" id="touchArea">
在此區域滑動手指
</div>
<div class="direction-container">
<div class="direction-indicator" id="directionIndicator">
<!-- 8個方向箭頭將通過JS動態添加 -->
</div>
<div class="direction-name" id="directionName">-</div>
<div class="debug-info" id="debugInfo">滑動方向: -</div>
</div>
<div class="stats">
<div class="stat-box">
<div>水平速度</div>
<div class="stat-value" id="horizontalSpeed">0</div>
<div class="unit">像素/秒</div>
</div>
<div class="stat-box">
<div>垂直速度</div>
<div class="stat-value" id="verticalSpeed">0</div>
<div class="unit">像素/秒</div>
</div>
<div class="stat-box">
<div>合成速度</div>
<div class="stat-value" id="overallSpeed">0</div>
<div class="unit">像素/秒</div>
</div>
<div class="stat-box">
<div>滑動角度</div>
<div class="stat-value" id="angle">0</div>
<div class="unit">度</div>
</div>
</div>
<div class="instructions">
<p><strong>使用説明:</strong>在觸摸區域快速滑動手指,系統將檢測您的滑動速度並判斷8個主要方向。</p>
<p>8個方向:北(N)、東北(NE)、東(E)、東南(SE)、南(S)、西南(SW)、西(W)、西北(NW)</p>
</div>
</div>
<div class="container">
<h2>滑動歷史記錄</h2>
<div class="history" id="history">
<!-- 歷史記錄將通過JS動態添加 -->
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const touchArea = document.getElementById('touchArea');
const horizontalSpeedElement = document.getElementById('horizontalSpeed');
const verticalSpeedElement = document.getElementById('verticalSpeed');
const overallSpeedElement = document.getElementById('overallSpeed');
const angleElement = document.getElementById('angle');
const directionIndicator = document.getElementById('directionIndicator');
const directionNameElement = document.getElementById('directionName');
const historyElement = document.getElementById('history');
const debugInfo = document.getElementById('debugInfo');
// 8個方向配置 - 按照順時針順序
const directions = [
{ name: "北(N)", angle: 0, symbol: "↑" },
{ name: "東北(NE)", angle: 45, symbol: "↗" },
{ name: "東(E)", angle: 90, symbol: "→" },
{ name: "東南(SE)", angle: 135, symbol: "↘" },
{ name: "南(S)", angle: 180, symbol: "↓" },
{ name: "西南(SW)", angle: 225, symbol: "↙" },
{ name: "西(W)", angle: 270, symbol: "←" },
{ name: "西北(NW)", angle: 315, symbol: "↖" }
];
let startX, startY, startTime;
let endX, endY, endTime;
let trailElements = [];
let historyCount = 0;
// 創建8個方向指示器
function createDirectionIndicators() {
directions.forEach((dir, index) => {
const arrow = document.createElement('div');
arrow.className = 'direction-arrow';
arrow.id = `dir-${index}`;
arrow.textContent = dir.symbol;
// 計算箭頭位置(圓形排列)- 北在頂部,順時針排列
const angleRad = (dir.angle * Math.PI) / 180;
const radius = 80;
const centerX = 100;
const centerY = 100;
// 修正位置計算
arrow.style.left = `${centerX + radius * Math.sin(angleRad)}px`;
arrow.style.top = `${centerY - radius * Math.cos(angleRad)}px`;
directionIndicator.appendChild(arrow);
});
}
// 重置方向指示器
function resetDirectionIndicators() {
directions.forEach((dir, index) => {
const arrow = document.getElementById(`dir-${index}`);
arrow.classList.remove('active');
});
directionNameElement.textContent = '-';
}
// 激活特定方向指示器
function activateDirection(directionIndex) {
resetDirectionIndicators();
const arrow = document.getElementById(`dir-${directionIndex}`);
arrow.classList.add('active');
directionNameElement.textContent = directions[directionIndex].name;
}
// 添加歷史記錄
function addHistoryRecord(speed, angle, direction) {
historyCount++;
const historyItem = document.createElement('div');
historyItem.className = 'history-item';
historyItem.textContent = `#${historyCount}: 速度 ${speed} px/s, 角度 ${angle}°, 方向 ${direction}`;
historyElement.prepend(historyItem);
// 限制歷史記錄數量
if (historyElement.children.length > 10) {
historyElement.removeChild(historyElement.lastChild);
}
}
// 創建觸摸軌跡點
function createTrail(x, y) {
const trail = document.createElement('div');
trail.className = 'touch-trail';
trail.style.left = `${x}px`;
trail.style.top = `${y}px`;
touchArea.appendChild(trail);
trailElements.push(trail);
// 限制軌跡點數量
if (trailElements.length > 20) {
const oldTrail = trailElements.shift();
if (oldTrail && oldTrail.parentNode) {
oldTrail.parentNode.removeChild(oldTrail);
}
}
// 漸隱效果
setTimeout(() => {
trail.style.opacity = '0.5';
}, 100);
setTimeout(() => {
trail.style.opacity = '0.2';
}, 300);
setTimeout(() => {
if (trail.parentNode) {
trail.parentNode.removeChild(trail);
const index = trailElements.indexOf(trail);
if (index > -1) {
trailElements.splice(index, 1);
}
}
}, 1000);
}
// 觸摸開始事件
touchArea.addEventListener('touchstart', function(e) {
e.preventDefault();
touchArea.classList.add('active');
const touch = e.touches[0];
const rect = touchArea.getBoundingClientRect();
startX = touch.clientX - rect.left;
startY = touch.clientY - rect.top;
startTime = new Date().getTime();
resetDirectionIndicators();
createTrail(startX, startY);
});
// 觸摸移動事件
touchArea.addEventListener('touchmove', function(e) {
e.preventDefault();
const touch = e.touches[0];
const rect = touchArea.getBoundingClientRect();
endX = touch.clientX - rect.left;
endY = touch.clientY - rect.top;
endTime = new Date().getTime();
createTrail(endX, endY);
calculateSpeed();
});
// 觸摸結束事件
touchArea.addEventListener('touchend', function(e) {
e.preventDefault();
touchArea.classList.remove('active');
// 短暫顯示最後的速度,然後重置
setTimeout(() => {
horizontalSpeedElement.textContent = '0';
verticalSpeedElement.textContent = '0';
overallSpeedElement.textContent = '0';
angleElement.textContent = '0';
resetDirectionIndicators();
debugInfo.textContent = '滑動方向: -';
}, 2000);
});
// 鼠標事件(用於桌面測試)
touchArea.addEventListener('mousedown', function(e) {
touchArea.classList.add('active');
const rect = touchArea.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
startTime = new Date().getTime();
resetDirectionIndicators();
createTrail(startX, startY);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
function onMouseMove(e) {
const rect = touchArea.getBoundingClientRect();
endX = e.clientX - rect.left;
endY = e.clientY - rect.top;
endTime = new Date().getTime();
createTrail(endX, endY);
calculateSpeed();
}
function onMouseUp(e) {
touchArea.classList.remove('active');
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
// 短暫顯示最後的速度,然後重置
setTimeout(() => {
horizontalSpeedElement.textContent = '0';
verticalSpeedElement.textContent = '0';
overallSpeedElement.textContent = '0';
angleElement.textContent = '0';
resetDirectionIndicators();
debugInfo.textContent = '滑動方向: -';
}, 2000);
}
// 計算速度
function calculateSpeed() {
if (!startX || !startY || !startTime) return;
const deltaTime = (endTime - startTime) / 1000; // 轉換為秒
if (deltaTime <= 0) return;
const deltaX = endX - startX;
const deltaY = endY - startY;
// 計算速度(像素/秒)
const horizontalSpeed = Math.abs(deltaX / deltaTime);
const verticalSpeed = Math.abs(deltaY / deltaTime);
const overallSpeed = Math.sqrt(horizontalSpeed * horizontalSpeed + verticalSpeed * verticalSpeed);
// 簡化角度計算 - 直接使用屏幕座標系
// 屏幕座標系:X向右為正,Y向下為正
let angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI;
if (angle < 0) angle += 360;
// 轉換為導航角度:0°=北,90°=東,180°=南,270°=西
// 關鍵修復:直接映射屏幕座標系到導航座標系
let navAngle = (angle + 90) % 360;
// 更新顯示
horizontalSpeedElement.textContent = Math.round(horizontalSpeed);
verticalSpeedElement.textContent = Math.round(verticalSpeed);
overallSpeedElement.textContent = Math.round(overallSpeed);
angleElement.textContent = Math.round(navAngle);
// 調試信息
debugInfo.textContent = `滑動方向: (${Math.round(deltaX)}, ${Math.round(deltaY)}) | 角度: ${Math.round(navAngle)}°`;
// 判斷8個方向
const directionIndex = getDirectionIndex(navAngle);
activateDirection(directionIndex);
// 添加歷史記錄(僅當速度足夠快時)
if (overallSpeed > 50) {
addHistoryRecord(Math.round(overallSpeed), Math.round(navAngle), directions[directionIndex].name);
}
// 更新起始點為當前點,以便連續計算速度
startX = endX;
startY = endY;
startTime = endTime;
}
// 根據角度獲取方向索引
function getDirectionIndex(angle) {
// 將角度轉換為0-360範圍
const normalizedAngle = (angle + 360) % 360;
// 每個方向覆蓋45度,偏移22.5度使方向居中
const sector = Math.floor((normalizedAngle + 22.5) / 45) % 8;
return sector;
}
// 初始化方向指示器
createDirectionIndicators();
// 測試函數 - 驗證方向是否正確
function testDirection(deltaX, deltaY, expectedDirection) {
let angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI;
if (angle < 0) angle += 360;
let navAngle = (angle + 90) % 360;
const directionIndex = getDirectionIndex(navAngle);
console.log(`測試 (${deltaX}, ${deltaY}): 角度=${Math.round(navAngle)}°, 方向=${directions[directionIndex].name}, 期望=${expectedDirection}`);
}
// 運行測試
console.log("方向測試:");
testDirection(0, -100, "北"); // 上
testDirection(100, -100, "東北"); // 右上
testDirection(100, 0, "東"); // 右
testDirection(100, 100, "東南"); // 右下
testDirection(0, 100, "南"); // 下
testDirection(-100, 100, "西南"); // 左下
testDirection(-100, 0, "西"); // 左
testDirection(-100, -100, "西北"); // 左上
});
</script>
</body>
</html>
以上代碼直接貼在html頁面內就能看到效果。