Google Search keyboard navigation

bring back the keyboard navigation in Google Search

当前为 2025-05-28 提交的版本,查看 最新版本

// ==UserScript==
// @name         Google Search keyboard navigation
// @namespace    http://tampermonkey.net/
// @version      2025-05-28-1
// @description  bring back the keyboard navigation in Google Search
// @author       victor141516
// @match        https://www.google.com/search?q=*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
  "use strict";
  const BASE_CLASS_NAME = "x-keyboard-utils";
  const ACTIVE_CLASS_NAME = "x-keyboard-utils-active";
  const RESULTS_CONTAINER_SELECTOR = "div[data-async-context^=query]";

  function appendCss() {
    const styles = `
      .${BASE_CLASS_NAME}.${ACTIVE_CLASS_NAME}:before {
        content: '';
        z-index: 100;
        display: block;
        position: absolute;
        pointer-events: none;
        border: 15px solid transparent;
        border-left-color: blue;
        left: -20px;
      }
  `;

    const styleElement = document.createElement("style");
    styleElement.textContent = styles;
    document.head.appendChild(styleElement);
  }

  (async () => {
    let activeIndex = 0;
    /** @type {Array<HTMLElement>} */
    let $results = [];

    document.addEventListener("keydown", (e) => {
      const isArrowDown = e.key === "ArrowDown";
      const isArrowUp = e.key === "ArrowUp";
      const isEnter = e.key === "Enter";

      if (!isArrowDown && !isArrowUp && !isEnter) return;
      e.preventDefault();

      if (isArrowDown) {
        activeIndex = Math.min(activeIndex + 1, $results.length - 1);
      } else if (isArrowUp) {
        activeIndex = Math.max(activeIndex - 1, 0);
      } else if (isEnter) {
        $results[activeIndex].querySelector("a").click();
      }
      updateArrow();
    });

    function updateArrow() {
      $results.forEach((e) => {
        e.classList.remove(ACTIVE_CLASS_NAME);
      });
      $results[activeIndex].classList.add(ACTIVE_CLASS_NAME);
      $results[activeIndex].scrollIntoView({
        behavior: "smooth",
        block: "center",
        inline: "center",
      });
    }

    function initializeResultElements() {
      $results = Array.from(
        document.querySelectorAll(
          `${RESULTS_CONTAINER_SELECTOR} div:has(>div[lang]>div[data-snc])`
        )
      );

      $results.forEach(($result) => {
        if ($result.classList.contains(BASE_CLASS_NAME)) return;

        $result.classList.add(BASE_CLASS_NAME);
      });
    }

    const targetNode = document.querySelector(RESULTS_CONTAINER_SELECTOR);
    const callback = (mutationList) => {
      for (const mutation of mutationList) {
        if (mutation.type === "childList") {
          initializeResultElements();
        }
      }
    };

    const observer = new MutationObserver(callback);
    observer.observe(targetNode, {
      attributes: false,
      childList: true,
      subtree: false,
    });

    appendCss();
    initializeResultElements();
    updateArrow();
  })();
})();