<!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>
    .src-pages-special-index-module__list–1mCGP {
      width: 100px;
      height: 100px;
      background-color: #ccc;
      margin: 10px;
      display: inline-block;
    }

    .focus {
      box-shadow: 0 0 10px 5px #f00;
    }
  </style>
</head>

<body>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <div class="src-pages-special-index-module__list–1mCGP"></div>
  <script>
    // 获取所有需要控制焦点的元素数组
    const elements = document.querySelectorAll(".src-pages-special-index-module__list–1mCGP");
    // 记录当前焦点所在的元素的索引
    let currentIndex = 0;

    // 计算每一行的第一个和最后一个元素的索引
    const rowIndices = Array.from(elements).reduce((rows, elem, i) => {
      if (i === 0 || elem.offsetTop > elements[i - 1].offsetTop) {
        rows.push([i]);
      } else {
        rows[rows.length - 1].push(i);
      }
      return rows;
    }, []);

    // 定义一个函数，根据元素在页面中的位置计算出它上下左右分别对应的元素
    function getFocusableElements(elem) {
      const rect = elem.getBoundingClientRect();
      const top = rect.top + window.pageYOffset - document.documentElement.clientTop;
      const left = rect.left + window.pageXOffset - document.documentElement.clientLeft;
      let firstRowIndex = -1;
      let lastRowIndex = -1;
      // 先找到当前元素所处的行
      rowIndices.forEach((indices, i) => {
        if (indices.indexOf(currentIndex) !== -1) {
          firstRowIndex = i;
          lastRowIndex = firstRowIndex + Math.floor((indices.length - 1) / 4)  // 计算当前行的最后一行的行号
        }
      });
      const focusableElements = {
        up: undefined,
        down: undefined,
        left: undefined,
        right: undefined
      };
      // 遍历所有元素，找到上下左右与当前元素相邻的元素
      elements.forEach((el, i) => {
        if (i !== currentIndex) {
          const elRect = el.getBoundingClientRect();
          const elTop = elRect.top + window.pageYOffset - document.documentElement.clientTop;
          const elLeft = elRect.left + window.pageXOffset - document.documentElement.clientLeft;
          // 如果两个元素在同一行
          if (elTop === top) {
            if (elLeft + elRect.width < left && (!focusableElements.left || elLeft > focusableElements.left.getBoundingClientRect().left)) {
              focusableElements.left = el;
            }
            if (elLeft > left + rect.width && (!focusableElements.right || elLeft < focusableElements.right.getBoundingClientRect().left)) {
              focusableElements.right = el;
            }
          }
          // 如果两个元素在相邻的行
          if (firstRowIndex !== -1 && lastRowIndex !== -1 && elTop === elements[rowIndices[firstRowIndex][0]].offsetTop + elements[rowIndices[firstRowIndex][0]].offsetHeight * (lastRowIndex - firstRowIndex + 1)) {
            if (elLeft < left + rect.width && (!focusableElements.down || elTop < focusableElements.down.getBoundingClientRect().top || (elTop === focusableElements.down.getBoundingClientRect().top && elLeft > focusableElements.down.getBoundingClientRect().left))) {
              focusableElements.down = el;
            }
          }
          // 如果两个元素在同一行上下方向，且间隔不超过一个元素的高度
          if (elTop < top && top - elTop <= elements[0].offsetHeight && i >= rowIndices[firstRowIndex][0] && i <= rowIndices[firstRowIndex][rowIndices[firstRowIndex].length - 1]) {
            if (!focusableElements.up || elTop > focusableElements.up.getBoundingClientRect().top) {
              focusableElements.up = el;
            }
          }
        }
      });
      return focusableElements;
    }

    // 给当前焦点元素加上焦点样式
    function setFocus(el) {
      el.classList.add("focus");
    }

    // 将焦点从当前元素移动到新元素上
    function moveFocus(newIndex) {
      elements[currentIndex].classList.remove("focus");
      currentIndex = newIndex;
      elements[currentIndex].classList.add("focus");
    }

    // 监听上下左右键的按键事件
    document.addEventListener("keydown", e => {
      const currentElement = elements[currentIndex];
      const focusableElements = getFocusableElements(currentElement);
      debugger
      switch (e.key) {
        case "ArrowUp":
          if (focusableElements.up) {
            moveFocus(Array.from(elements).indexOf(focusableElements.up));
          }
          break;
        case "ArrowDown":
          if (focusableElements.down) {
            moveFocus(Array.from(elements).indexOf(focusableElements.down));
          }
          break;
        case "ArrowLeft":
          if (focusableElements.left) {
            moveFocus(Array.from(elements).indexOf(focusableElements.left));
          }
          break;
        case "ArrowRight":
          if (focusableElements.right) {
            moveFocus(Array.from(elements).indexOf(focusableElements.right));
          }
          break;
      }
    });

    // 给第一个元素设置初始焦点
    setFocus(elements[0]);
  </script>
</body>

</html>。       这个demo中上下键对应有问题，左右键是对的