DuckDuckGo优化

便捷返回顶部/跨引擎一键搜

当前为 2025-04-15 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name               DuckDuckGo Optimization
// @name:zh-CN         DuckDuckGo优化
// @name:zh-TW         DuckDuckGo優化
// @description        Double Click To Return The Top / Shortcuts To Other Search Engines
// @description:zh-CN  便捷返回顶部/跨引擎一键搜
// @description:zh-TW  便捷返回頂部/跨引擎一鍵搜
// @version            1.1.0
// @icon               https://raw.githubusercontent.com/MiPoNianYou/UserScripts/refs/heads/main/Icons/DuckDuckGoOptimizationIcon.svg
// @author             念柚
// @namespace          https://github.com/MiPoNianYou/UserScripts
// @supportURL         https://github.com/MiPoNianYou/UserScripts/issuesx
// @license            GPL-3.0
// @match              https://duckduckgo.com/*
// @grant              GM_addStyle
// ==/UserScript==

(function () {
  "use strict";

  const RightAreaRatio = 0.2;
  const InteractiveElementsSelector =
    'a, button, input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])';
  const SearchFormSelector = "#search_form";
  const SearchInputSelector = "#search_form_input";
  const SearchBtnGroupClass = "SearchBtnGroup";
  const SearchBtnClass = "SearchBtn";
  const DebounceDelay = 250;

  document.addEventListener(
    "dblclick",
    function (Event) {
      const WindowWidth = window.innerWidth;
      const TriggerBoundary = WindowWidth * (1 - RightAreaRatio);
      if (
        Event.clientX > TriggerBoundary &&
        !Event.target.closest(InteractiveElementsSelector)
      ) {
        window.scrollTo({
          top: 0,
          behavior: "smooth",
        });
      }
    },
    { passive: true }
  );

  const SearchButtonStyle = `
  .${SearchBtnGroupClass} {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin: 12px auto;
    padding: 0 10px;
    max-width: 800px;
    justify-content: center;
  }
  .${SearchBtnClass} {
    padding: 10px 20px;
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 24px;
    color: #e6e6e6;
    font-family: inherit;
    font-size: 14px;
    cursor: pointer;
    transition: all 0.2s ease;
    min-width: 120px;
    text-align: center;
    flex-grow: 1;
    flex-basis: 120px;
    box-sizing: border-box;
  }
  .${SearchBtnClass}:hover {
    background: rgba(255, 255, 255, 0.15);
    border-color: rgba(255, 255, 255, 0.3);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  }
  @media (prefers-color-scheme: light) {
    .${SearchBtnClass} {
      background: #f8f9fa;
      border-color: #ddd;
      color: #444;
    }
    .${SearchBtnClass}:hover {
      background: #f1f3f4;
      border-color: #dadce0;
    }
  }
`;
  GM_addStyle(SearchButtonStyle);

  const EngineList = [
    { Name: "Google", Url: "https://www.google.com/search?q=" },
    { Name: "Bing", Url: "https://www.bing.com/search?q=" },
    { Name: "Baidu", Url: "https://www.baidu.com/s?wd=" },
  ];

  function Debounce(Func, Wait) {
    let Timeout;
    return function ExecutedFunction(...Args) {
      const Later = () => {
        clearTimeout(Timeout);
        Func(...Args);
      };
      clearTimeout(Timeout);
      Timeout = setTimeout(Later, Wait);
    };
  }

  function CreateSearchButtons() {
    const SearchForm = document.querySelector(SearchFormSelector);

    if (!SearchForm || document.querySelector(`.${SearchBtnGroupClass}`)) {
      return;
    }

    const SearchInput = SearchForm.querySelector(SearchInputSelector);
    if (!SearchInput) {
      return;
    }

    const BtnGroup = document.createElement("div");
    BtnGroup.className = SearchBtnGroupClass;

    EngineList.forEach((Engine) => {
      const Button = document.createElement("button");
      Button.className = SearchBtnClass;
      Button.textContent = `使用 ${Engine.Name} 搜索`;
      Button.type = "button";

      Button.addEventListener("click", (Event) => {
        Event.preventDefault();
        const Query = SearchInput.value.trim();

        if (Query) {
          const SearchUrl = `${Engine.Url}${encodeURIComponent(Query)}`;
          window.open(SearchUrl, "_blank", "noopener,noreferrer");
        }
      });
      BtnGroup.appendChild(Button);
    });

    SearchForm.parentNode.insertBefore(BtnGroup, SearchForm.nextSibling);
  }

  const DebouncedCreateSearchButtons = Debounce(
    CreateSearchButtons,
    DebounceDelay
  );

  const Observer = new MutationObserver(() => {
    DebouncedCreateSearchButtons();
  });

  Observer.observe(document.body, {
    childList: true,
    subtree: true,
  });

  if (document.readyState === "loading") {
    window.addEventListener("DOMContentLoaded", CreateSearchButtons);
  } else {
    CreateSearchButtons();
  }
})();