油猴多搜索引擎弹窗搜索

实现所有搜索页面都可以直接调用搜索引擎,不需要在另外打开新的页面打开。

// ==UserScript==
// @name         多搜索引擎弹窗搜索
// @namespace    http://tampermonkey.net/
// @version      2.9
// @description  悬浮图标与弹窗双向联动拖拽,拖动弹窗时图标同步移动
// @author       You
// @match        *://*/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==
(function () {
    'use strict';
    const ENGINES = [
        { name: "百度", url: "https://www.baidu.com/s?wd={q}" },
        { name: "谷歌", url: "https://www.google.com/search?q={q}" }
    ];
    const HOST_ID = '__ms_shadow_host__';

    const BTN_SIZE = 44;
    const POP_W = 360;
    const GAP = 8;

    // 统一状态:按钮位置为基准,弹窗位置由按钮位置计算
    let state = {
        btnLeft: 0,
        btnTop: 0,
        popLeft: 0,
        popTop: 0
    };

    let dragBtn = { on: false, moved: false, ox: 0, oy: 0 };
    let dragPop = { on: false, ox: 0, oy: 0 };

    function getSearchInputText() {
        const selectors = [
            'input[name="wd"]',
            'input[name="q"]',
            'input[name="query"]',
            'input[type="search"]',
            'input[placeholder*="请输入"]',
            'input[placeholder*="搜索"]',
            'input[placeholder*="企业名称"]',
            '.el-input__inner'
        ];
        for (const sel of selectors) {
            const el = document.querySelector(sel);
            if (el && el.value?.trim()) return el.value.trim();
        }
        return '';
    }

    function applyLayout(btn, pop) {
        btn.style.left = state.btnLeft + 'px';
        btn.style.top = state.btnTop + 'px';
        pop.style.left = state.popLeft + 'px';
        pop.style.top = state.popTop + 'px';
    }

    // 根据按钮位置重新计算弹窗位置
    function positionPopupFromBtn(pop) {
        const popH = pop.offsetHeight || 180;
        let popTop = state.btnTop - popH - GAP;
        let popLeft = state.btnLeft;

        // 水平边界
        popLeft = Math.max(8, Math.min(popLeft, window.innerWidth - POP_W - 8));

        // 上方放不下就放按钮下方
        if (popTop < 8) {
            popTop = state.btnTop + BTN_SIZE + GAP;
        }

        // 垂直边界兜底
        if (popTop + popH > window.innerHeight - 8) {
            popTop = Math.max(8, window.innerHeight - popH - 8);
        }

        state.popLeft = popLeft;
        state.popTop = popTop;
    }

    function saveBtnPos() {
        localStorage.setItem(
            'ms_pos',
            JSON.stringify({
                l: Math.round(state.btnLeft),
                t: Math.round(state.btnTop)
            })
        );
    }

    function init() {
        if (document.getElementById(HOST_ID)) return;
        if (!document.body) return;

        const host = document.createElement('div');
        host.id = HOST_ID;
        host.style.cssText = [
            'position:fixed!important',
            'top:0!important',
            'left:0!important',
            'width:0!important',
            'height:0!important',
            'overflow:visible!important',
            'z-index:2147483647!important',
            'pointer-events:none!important',
            'background:0 0!important',
            'border:0!important',
            'margin:0!important',
            'padding:0!important'
        ].join(';');
        document.body.appendChild(host);

        const shadow = host.attachShadow({ mode: 'open' });

        const style = document.createElement('style');
        style.textContent = `
            :host { all: initial; }
            * { box-sizing: border-box; margin: 0; padding: 0; }
            .s-btn {
                position: fixed; width: 44px; height: 44px; border-radius: 50%;
                background: #2563eb; color: #fff; display: flex; align-items: center;
                justify-content: center; cursor: grab; z-index: 2147483647;
                box-shadow: 0 2px 10px rgba(0,0,0,.25); user-select: none;
                font-size: 18px; pointer-events: auto; line-height: 1; border: none;
                font-family: system-ui, -apple-system, sans-serif;
                opacity:0.4;
                transition: opacity 0.2s;
            }
            .s-btn:hover { opacity:0.65; }
            .s-btn:active { cursor: grabbing; }
            .s-pop {
                position: fixed; width: 360px; background: rgba(255,255,255,0.75); border-radius: 10px;
                box-shadow: 0 4px 24px rgba(0,0,0,.22); z-index: 2147483647;
                padding: 14px 14px 12px; display: none; pointer-events: auto;
                font-family: system-ui, -apple-system, sans-serif;
                cursor: grab;
            }
            .s-pop:active { cursor: grabbing; }
            .s-pop input {
                width: 100%; padding: 9px 12px; border: 1px solid rgba(204,204,204,.6);
                border-radius: 6px; font-size: 14px; margin-bottom: 10px;
                outline: none; font-family: inherit;
                background: rgba(255,255,255,0.55);
                color: #111;
                pointer-events:auto;
                cursor:text;
            }
            .s-pop input:focus {
                border-color: rgba(37,99,235,.8);
                box-shadow: 0 0 0 2px rgba(37,99,235,.18);
                background: rgba(255,255,255,0.75);
            }
            .s-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
            .s-grid button {
                padding: 7px 4px; border: none; border-radius: 6px;
                background: rgba(37,99,235,.72); color: #fff; cursor: pointer;
                font-size: 13px; font-family: inherit; pointer-events: auto;
                transition: background .15s;
            }
            .s-grid button:hover { background: rgba(29,78,216,.88); }
            .s-close {
                position: absolute; top: 6px; right: 8px; cursor: pointer;
                font-size: 18px; color: rgba(153,153,153,0.85); pointer-events: auto;
                line-height: 1; background: none; border: none; padding: 4px;
            }
            .s-close:hover { color: #333; }
        `;
        shadow.appendChild(style);

        const btn = document.createElement('div');
        btn.className = 's-btn';
        btn.textContent = '🔍';
        shadow.appendChild(btn);

        const pop = document.createElement('div');
        pop.className = 's-pop';
        pop.innerHTML =
            '<span class="s-close">×</span>' +
            '<input placeholder="关键词,可自动读取页面搜索框内容">' +
            '<div class="s-grid"></div>';
        shadow.appendChild(pop);

        const input = pop.querySelector('input');
        const grid = pop.querySelector('.s-grid');
        const closeBtn = pop.querySelector('.s-close');

        ENGINES.forEach(en => {
            const b = document.createElement('button');
            b.textContent = en.name;
            b.dataset.url = en.url;
            grid.appendChild(b);
        });

        closeBtn.onclick = () => {
            pop.style.display = 'none';
        };

        // 初始化按钮位置
        const saved = JSON.parse(localStorage.getItem('ms_pos') || '{}');
        if (saved.l != null && saved.t != null) {
            state.btnLeft = saved.l;
            state.btnTop = saved.t;
        } else {
            state.btnLeft = window.innerWidth - BTN_SIZE - 20;
            state.btnTop = window.innerHeight - BTN_SIZE - 80;
        }

        positionPopupFromBtn(pop);
        applyLayout(btn, pop);

        // 按钮拖拽
        btn.addEventListener('mousedown', e => {
            if (e.button !== 0) return;
            dragBtn.on = true;
            dragBtn.moved = false;
            dragBtn.ox = e.clientX - state.btnLeft;
            dragBtn.oy = e.clientY - state.btnTop;
            e.preventDefault();
        });

        // 弹窗拖拽:明确带动按钮
        pop.addEventListener('mousedown', e => {
            if (e.target === input || e.target.closest('button')) return;
            if (e.button !== 0) return;

            dragPop.on = true;
            dragPop.ox = e.clientX - state.popLeft;
            dragPop.oy = e.clientY - state.popTop;
            e.preventDefault();
        });

        document.addEventListener('mousemove', e => {
            if (dragBtn.on) {
                dragBtn.moved = true;
                state.btnLeft = Math.max(0, Math.min(e.clientX - dragBtn.ox, window.innerWidth - BTN_SIZE));
                state.btnTop = Math.max(0, Math.min(e.clientY - dragBtn.oy, window.innerHeight - BTN_SIZE));

                positionPopupFromBtn(pop);
                applyLayout(btn, pop);
            }

            if (dragPop.on) {
                // 先移动弹窗
                state.popLeft = Math.max(0, Math.min(e.clientX - dragPop.ox, window.innerWidth - POP_W));
                state.popTop = Math.max(0, Math.min(e.clientY - dragPop.oy, window.innerHeight - (pop.offsetHeight || 180)));

                // 按钮始终跟随弹窗底部
                state.btnLeft = state.popLeft;
                state.btnTop = state.popTop + (pop.offsetHeight || 180) + GAP;

                // 按钮边界
                state.btnLeft = Math.min(state.btnLeft, window.innerWidth - BTN_SIZE);
                state.btnTop = Math.min(state.btnTop, window.innerHeight - BTN_SIZE);

                applyLayout(btn, pop);
            }
        });

        document.addEventListener('mouseup', () => {
            if (dragBtn.on && dragBtn.moved) saveBtnPos();
            if (dragPop.on) saveBtnPos();

            dragBtn.on = false;
            dragPop.on = false;
        });

        btn.addEventListener('click', e => {
            if (dragBtn.moved) return;

            let txt = '';
            try { txt = window.getSelection().toString().trim(); } catch (_) {}
            if (!txt) txt = getSearchInputText();
            if (txt) input.value = txt;

            pop.style.display = 'block';
            positionPopupFromBtn(pop);
            applyLayout(btn, pop);
            input.focus();
        });

        grid.addEventListener('click', e => {
            if (e.target.tagName !== 'BUTTON') return;
            const q = input.value.trim();
            if (!q) return;
            window.open(e.target.dataset.url.replace('{q}', encodeURIComponent(q)), '_blank');
            pop.style.display = 'none';
        });

        input.addEventListener('keydown', e => {
            if (e.key === 'Enter') grid.querySelector('button').click();
        });

        document.addEventListener('mousedown', e => {
            if (!host.contains(e.target)) pop.style.display = 'none';
        });

        const obs = new MutationObserver(() => {
            if (!document.getElementById(HOST_ID) && document.body) {
                obs.disconnect();
                init();
            }
        });
        obs.observe(document.documentElement, { childList: true, subtree: true });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    setInterval(() => {
        if (!document.getElementById(HOST_ID) && document.body) init();
    }, 1000);
})();
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇