// ==UserScript== // @name 搜索引擎切换器 / Search Engine Switcher // @namespace http://tampermonkey.net/ // @version 0.7.1 // @description 一键切换多个搜索引擎!支持Google、Bing、百度、360搜索等主流平台。可拖拽、记忆位置,支持选中文本搜索。性能优化版本。 // @author AI优化版-原作者:WUJI // @match *://www.google.com*/search* // @match *://www.bing.com/search* // @match *://cn.bing.com/search* // @match *://*yandex.com/search* // @match *://www.baidu.com/s* // @match *://www.baidu.com/baidu* // @match *://www.sogou.com/web* // @match *://search.bilibili.com/all* // @match *://www.zhihu.com/search* // @match *://www.so.com/s* // @grant none // @run-at document-body // @license MIT // @icon data:image/svg+xml,🔎 // ==/UserScript== (function() { 'use strict'; // ==================== 配置项 ==================== const CONFIG = { ICON_SIZE: '32px', LIST_MAX_WIDTH: '160px', FONT_SIZE: '14px', STORAGE_KEY: 'searchSwitcherPos', HALF_HIDE_TRANSLATE: '-16px', // 性能优化:防抖延迟 DEBOUNCE_DELAY: 100, // 拖拽阈值(像素),超过才认为是拖动,避免误触 DRAG_THRESHOLD: 5, // 启用硬件加速 ENABLE_HW_ACCELERATION: true }; // ==================== 搜索引擎定义 ==================== const SEARCH_ENGINES = [ { name: "Bing", url: "https://www.bing.com/search?q=", keyName: "q", testUrl: /https:\/\/(www|cn)\.bing\.com\/search.*/ }, { name: "百度", url: "https://www.baidu.com/s?wd=", keyName: "wd", testUrl: /https:\/\/www\.baidu\.com\/(s|baidu).*/ }, { name: "Yandex", url: "https://yandex.com/search?text=", keyName: "text", testUrl: /https:\/\/yandex\.com\/search.*/ }, { name: "360搜索", url: "https://www.so.com/s?q=", keyName: "q", testUrl: /https:\/\/www\.so\.com\/s.*/ }, { name: "Google", url: "https://www.google.com/search?q=", keyName: "q", testUrl: /https:\/\/www\.google\.(com|com\.hk)\/search.*/ }, { name: "搜狗", url: "https://www.sogou.com/web?query=", keyName: "query", testUrl: /https:\/\/www\.sogou\.com\/web.*/ }, { name: "哔站", url: "https://search.bilibili.com/all?keyword=", keyName: "keyword", testUrl: /https:\/\/search\.bilibili\.com\/all.*/ }, { name: "知乎", url: "https://www.zhihu.com/search?q=", keyName: "q", testUrl: /https:\/\/www\.zhihu\.com\/search.*/ } ]; // ==================== 工具函数 ==================== function getCurrentEngine() { const url = window.location.href; for (const engine of SEARCH_ENGINES) { if (engine.testUrl.test(url)) return engine; } return null; } function extractKeywordFromUrl(engine) { try { const url = new URL(window.location.href); return url.searchParams.get(engine.keyName) || ''; } catch { return ''; } } function getKeywords() { const currentEngine = getCurrentEngine(); if (currentEngine) { const kw = extractKeywordFromUrl(currentEngine); if (kw) return kw; } const selected = window.getSelection().toString().trim(); if (selected) return selected; return prompt('请输入搜索关键词:', '') || ''; } function isDarkMode() { return window.matchMedia('(prefers-color-scheme: dark)').matches; } function loadPosition() { try { const pos = JSON.parse(localStorage.getItem(CONFIG.STORAGE_KEY)); if (pos && typeof pos.left === 'number' && typeof pos.top === 'number') { return pos; } } catch {} return { left: 0, top: 100 }; } function savePosition(left, top) { localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify({ left, top })); } // 防抖函数 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // ==================== 样式创建 ==================== function createStyle() { const style = document.createElement('style'); style.textContent = ` :root { --list-bg-light: rgba(255, 255, 255, 0.95); --list-bg-dark: rgba(40, 40, 40, 0.95); --text-light: #333; --text-dark: #eee; --hover-light: rgba(0, 0, 0, 0.1); --hover-dark: rgba(255, 255, 255, 0.1); } #search-app-box { position: fixed; top: 0; left: 0; width: ${CONFIG.ICON_SIZE}; height: ${CONFIG.ICON_SIZE}; background: transparent !important; z-index: 2147483647; cursor: move; font-size: ${CONFIG.FONT_SIZE}; transition: transform 0.3s ease-in-out, left 0.3s ease-in-out, top 0.3s ease-in-out; user-select: none; transform: translateX(${CONFIG.HALF_HIDE_TRANSLATE}); will-change: transform; ${CONFIG.ENABLE_HW_ACCELERATION ? 'backface-visibility: hidden; perspective: 1px;' : ''} } #search-app-box.dragging { transition: none; } #search-app-box:not(.half-hidden) { transform: translateX(0); } #search-app-icon { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; font-size: ${CONFIG.ICON_SIZE}; background: transparent !important; color: inherit; } #search-engine-list { position: absolute; top: 2%; left: ${CONFIG.ICON_SIZE}; min-width: 80px; max-width: ${CONFIG.LIST_MAX_WIDTH}; max-height: 70vh; overflow-y: auto; background-color: var(--list-bg-light); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); opacity: 0; visibility: hidden; transform: translateX(-10px); transition: opacity 0.2s, visibility 0.2s, transform 0.2s; backdrop-filter: blur(4px); white-space: nowrap; } #search-engine-list a { display: block; padding: 10px 16px; color: var(--text-light); text-decoration: none; transition: background-color 0.2s; cursor: pointer; } #search-engine-list a:hover { background-color: var(--hover-light); } body.dark-mode #search-engine-list { background-color: var(--list-bg-dark); } body.dark-mode #search-engine-list a { color: var(--text-dark); } body.dark-mode #search-engine-list a:hover { background-color: var(--hover-dark); } /* 添加滚动条样式 */ #search-engine-list::-webkit-scrollbar { width: 6px; } #search-engine-list::-webkit-scrollbar-track { background: rgba(0,0,0,0.1); border-radius: 3px; } #search-engine-list::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.3); border-radius: 3px; } #search-engine-list::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.5); } `; document.head.appendChild(style); } // ==================== 创建UI ==================== function createSearchBox() { const box = document.createElement('div'); box.id = 'search-app-box'; box.classList.add('half-hidden'); const savedPos = loadPosition(); box.style.left = savedPos.left + 'px'; box.style.top = savedPos.top + 'px'; const icon = document.createElement('div'); icon.id = 'search-app-icon'; icon.innerText = '🔎'; box.appendChild(icon); const listContainer = document.createElement('div'); listContainer.id = 'search-engine-list'; const currentKeywords = getKeywords(); const currentEngine = getCurrentEngine(); for (const engine of SEARCH_ENGINES) { if (currentEngine && engine.name === currentEngine.name) continue; const a = document.createElement('a'); a.href = engine.url + encodeURIComponent(currentKeywords); a.title = `在${engine.name}中搜索: ${currentKeywords}`; a.innerText = engine.name; a.dataset.url = a.href; // 备用 listContainer.appendChild(a); } box.appendChild(listContainer); document.body.appendChild(box); let isDragging = false; let isMouseOverBox = false; let rafId = null; let startX, startY, startLeft, startTop; let dragDistance = 0; // 记录拖拽距离,用于判断是否为拖动操作 // 显示图标和列表 function showAll() { box.classList.remove('half-hidden'); listContainer.style.opacity = '1'; listContainer.style.visibility = 'visible'; listContainer.style.transform = 'translateX(0)'; } // 隐藏列表,图标半隐藏(如果条件满足) function hideAll() { if (isDragging || isMouseOverBox) return; box.classList.add('half-hidden'); listContainer.style.opacity = '0'; listContainer.style.visibility = 'hidden'; listContainer.style.transform = 'translateX(-10px)'; } // 鼠标进入 box.addEventListener('mouseenter', () => { isMouseOverBox = true; showAll(); }); // 鼠标离开 box.addEventListener('mouseleave', () => { isMouseOverBox = false; hideAll(); }); // 拖拽开始 box.addEventListener('mousedown', (e) => { if (e.button !== 0) return; e.preventDefault(); isDragging = true; dragDistance = 0; // 重置拖拽距离 showAll(); box.classList.add('dragging'); startX = e.clientX; startY = e.clientY; startLeft = box.offsetLeft; startTop = box.offsetTop; function onMouseMove(e) { if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { const dx = e.clientX - startX; const dy = e.clientY - startY; dragDistance = Math.sqrt(dx * dx + dy * dy); // 计算拖拽距离 // 使用 transform 移动,避免重排 box.style.transform = `translate(${dx}px, ${dy}px)`; rafId = null; }); } function onMouseUp(e) { if (rafId) { cancelAnimationFrame(rafId); rafId = null; } document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); // 如果拖拽距离小于阈值,则认为是点击而非拖动 if (dragDistance < CONFIG.DRAG_THRESHOLD) { isDragging = false; box.classList.remove('dragging'); // 检查鼠标是否还在 box 内,如果是则显示列表 const elemAtMouse = document.elementFromPoint(e.clientX, e.clientY); const mouseInBox = box.contains(elemAtMouse); if (mouseInBox) { isMouseOverBox = true; showAll(); } else { isMouseOverBox = false; hideAll(); } return; } // 计算最终位置并更新 left/top,清除 transform const finalLeft = startLeft + (parseInt(box.style.transform.match(/translate\(([-\d.]+)px/)?.[1] || 0)); const finalTop = startTop + (parseInt(box.style.transform.match(/translate\([-\d.]+px,\s*([-\d.]+)px/)?.[1] || 0)); // 确保元素不会被拖出屏幕边界 const clampedLeft = Math.max(0, Math.min(finalLeft, window.innerWidth - parseInt(CONFIG.ICON_SIZE))); const clampedTop = Math.max(0, Math.min(finalTop, window.innerHeight - parseInt(CONFIG.ICON_SIZE))); box.style.left = clampedLeft + 'px'; box.style.top = clampedTop + 'px'; box.style.transform = ''; // 清除拖拽偏移 isDragging = false; box.classList.remove('dragging'); savePosition(clampedLeft, clampedTop); // 检查鼠标是否还在 box 内 const elemAtMouse = document.elementFromPoint(e.clientX, e.clientY); const mouseInBox = box.contains(elemAtMouse); if (!mouseInBox) { isMouseOverBox = false; hideAll(); } else { isMouseOverBox = true; } } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); // 事件委托处理列表点击 listContainer.addEventListener('click', (e) => { const target = e.target.closest('a'); if (!target) return; e.preventDefault(); const url = target.href; if (e.ctrlKey || e.metaKey) { window.open(url, '_blank'); } else { window.location.href = url; } }); // 优化键盘事件监听 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !box.classList.contains('half-hidden')) { hideAll(); } }); } // ==================== 主题切换 ==================== function updateTheme() { document.body.classList.toggle('dark-mode', isDarkMode()); } // ==================== 初始化 ==================== function init() { createStyle(); createSearchBox(); updateTheme(); // 使用防抖优化主题变化监听 const debouncedUpdateTheme = debounce(updateTheme, CONFIG.DEBOUNCE_DELAY); window.matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', debouncedUpdateTheme); } // 直接初始化(@run-at document-body 保证 body 存在) if (document.body) { init(); } else { // 极少数情况 fallback window.addEventListener('DOMContentLoaded', init); } })();