You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React实现XMB(PSP/PS3)菜单时箭头键导航跳项问题求助

React XMB菜单箭头键导航跳项问题解决

问题描述

在React中实现类似PSP/PS3的XMB菜单时,遇到箭头键导航跳项问题:从Item 1直接滚动到Item 3(跳过Item 2),返回时从Item 3直接回到Item 1。

已尝试方案

方案1:索引除法计算

$(".container").on("keydown", function (e) {
        if (e.which === arrowKeys.left) {
          _position = --_position / _length;
        }

        if (e.which === arrowKeys.right) {
          _position = ++_position / _length;
        }

        move();
      });

方案2:模运算计算

$(".container").on("keydown", function (e) {
        if (e.which === arrowKeys.left) {
          _position = (1-_position) % _length;
        }

        if (e.which === arrowKeys.right) {
          _position = (1-_position) % _length;
        }

        move();
      });

方案3:长度减1模运算

$(".container").on("keydown", function (e) {
        if (e.which === arrowKeys.left) {
          _position = --_position % (_length-1);
        }

        if (e.which === arrowKeys.right) {
          _position = ++_position % (_length-1);
        }

        move();
      });

方案4:修改tabIndex属性

<div class="wrapper">
        <div class="container">
          <div class="item" tabIndex="1">
            item 1
          </div>
          <div class="item" tabIndex="3">
            item 2
          </div>
          <div class="item" tabIndex="2">
            item 3
          </div>
          <div class="item" tabIndex="5">
            item 4
          </div>
          <div class="item" tabIndex="4">
            item 5
          </div>
          <div class="item" tabIndex="7">
            item 6
          </div>
          <div class="item" tabIndex="6">
            item 7
          </div>
          <div class="item" tabIndex="9">
            item 8
          </div>
          <div class="item" tabIndex="8">
            item 9
          </div>
          <div class="item" tabIndex="10">
            item 10
          </div>
        </div>
      </div>

问题原因

  1. 原生导航干扰:未阻止箭头键的默认导航行为,浏览器原生焦点切换逻辑与自定义导航逻辑冲突,导致跳项。
  2. 索引计算错误:之前的索引计算逻辑不符合循环导航需求,无法正确定位到相邻项。
  3. tabIndex混乱:手动设置的tabIndex顺序与DOM顺序不一致,进一步打乱了原生焦点导航的顺序。

解决方案

推荐:React原生实现方式

使用React状态管理和事件监听,避免jQuery与React DOM操作冲突:

import { useState, useEffect, useRef } from 'react';

const XMBMenu = () => {
  const [activeIndex, setActiveIndex] = useState(0);
  const itemsRef = useRef([]);
  const menuItems = Array.from({ length: 10 }, (_, idx) => `item ${idx + 1}`);
  const itemCount = menuItems.length;

  useEffect(() => {
    const handleKeyDown = (e) => {
      if (![37, 39].includes(e.keyCode)) return;
      
      // 阻止浏览器默认焦点切换行为
      e.preventDefault();

      if (e.keyCode === 39) {
        // 右箭头:切换到下一项
        setActiveIndex(prev => (prev + 1) % itemCount);
      } else {
        // 左箭头:切换到上一项,避免负数索引
        setActiveIndex(prev => (prev - 1 + itemCount) % itemCount);
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [itemCount]);

  // 切换焦点到当前激活项
  useEffect(() => {
    itemsRef.current[activeIndex]?.focus();
  }, [activeIndex]);

  return (
    <div className="wrapper">
      <div className="container">
        {menuItems.map((item, idx) => (
          <div
            key={idx}
            className={`item ${idx === activeIndex ? 'active' : ''}`}
            tabIndex="0"
            ref={el => itemsRef.current[idx] = el}
          >
            {item}
          </div>
        ))}
      </div>
    </div>
  );
};

export default XMBMenu;

兼容jQuery的修正方案

若坚持使用jQuery,需修正索引计算并阻止默认行为:

const itemCount = $(".item").length;
let currentPosition = 0;

$(".container").on("keydown", function (e) {
  const arrowLeft = 37;
  const arrowRight = 39;

  if (e.which !== arrowLeft && e.which !== arrowRight) return;
  
  // 阻止默认导航行为
  e.preventDefault();

  if (e.which === arrowLeft) {
    // 左箭头:循环到上一项
    currentPosition = (currentPosition - 1 + itemCount) % itemCount;
  } else {
    // 右箭头:循环到下一项
    currentPosition = (currentPosition + 1) % itemCount;
  }

  // 聚焦到当前项
  $(".item").eq(currentPosition).focus();
  move(); // 执行你的菜单滚动逻辑
});

关键注意点

  • 所有菜单项设置tabIndex="0",确保它们可被聚焦且按DOM顺序导航,不要手动设置乱序的tabIndex。
  • 必须阻止箭头键的默认行为,避免浏览器原生焦点切换干扰自定义逻辑。
  • 索引计算需处理循环边界,用(prevIndex ±1 + 总数量) % 总数量避免负数或越界。

内容的提问来源于stack exchange,提问作者Dexter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 08:25:22