Stories

Detail Return Return

實現八方位判斷滑動方向和速度檢測 - Stories Detail

八方位判斷滑動方向,用判斷用户鼠標或者觸摸屏方位和滑動速度。
實用場景為:平滑滾動時候判斷方位及漂移位置。
image.png

<!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頁面內就能看到效果。

Add a new Comments

Some HTML is okay.