// ==UserScript== // @name 豆包右侧动画导航栏 // @namespace https://github.com/yourname/doubao-smooth-nav-poll // @version 1.7.6.2 // @description 为豆包添加右侧悬浮横线导航,支持平滑动画、点击定位、自动轮询刷新对话,零侵入界面 // @author 友野YouyEr // @match https://www.doubao.com/chat/* // @grant none // ==/UserScript== (function() { 'use strict'; const style = document.createElement('style'); style.textContent = ` /* 样式与 7.6 完全一致 */ .usr-nav-native { position: fixed; right: 10px; top: 50%; transform: translateY(-50%); z-index: 10; pointer-events: none; } .usr-nav-indicator { width: 16px; transition: width 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), background 0.3s ease, box-shadow 0.3s ease, backdrop-filter 0.3s ease; overflow-y: hidden; overflow-x: hidden; background: transparent; backdrop-filter: none; box-shadow: none; border-radius: 10px 0 0 10px; display: flex; flex-direction: column; align-items: stretch; max-height: 288px; cursor: pointer; pointer-events: auto; scroll-behavior: smooth; } .usr-nav-indicator:hover { width: 260px; overflow-y: auto; background: linear-gradient(135deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0.35) 100%); backdrop-filter: blur(20px) saturate(180%); box-shadow: -4px 0 20px rgba(0,0,0,0.04), inset 0 0 0 0.5px rgba(255,255,255,0.6); } .usr-nav-item { height: 48px; width: 100%; position: relative; display: flex; align-items: center; flex-shrink: 0; transition: background 0.18s ease, border-radius 0.18s ease; padding-left: 6px; box-sizing: border-box; border-radius: 6px; } .usr-nav-item .usr-nav-text { display: none; opacity: 0; transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.2, 0.9, 0.4, 1); transform: translateX(-6px); } .usr-nav-indicator:hover .usr-nav-text { display: block; opacity: 1; transform: translateX(0); } .usr-nav-dash-marker { position: absolute; right: 3px; top: 50%; transform: translateY(-50%); width: 10px; height: 3px; border-radius: 3px; background: rgba(100, 110, 125, 0.5); transition: width 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), height 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.2s ease, box-shadow 0.2s ease; z-index: 2; } .usr-nav-item.active .usr-nav-dash-marker { width: 14px; height: 4px; background: linear-gradient(90deg, #165DFF, #4080FF); box-shadow: 0 0 6px rgba(22,93,255,0.25); } .usr-nav-text { font-size: 13px; font-weight: 400; line-height: 1.5; color: rgba(0, 0, 0, 0.38); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 16px; margin-left: 6px; -webkit-font-smoothing: antialiased; } .usr-nav-indicator:hover .usr-nav-item.active .usr-nav-text { color: #165DFF; font-weight: 500; } .usr-nav-indicator:hover .usr-nav-item:hover:not(.active) { background: rgba(0, 0, 0, 0.03); border-radius: 8px; } .usr-nav-indicator:hover .usr-nav-item:hover:not(.active) .usr-nav-text { color: #1a1a1a; } .usr-nav-indicator:hover .usr-nav-item:hover .usr-nav-dash-marker { background: rgba(22,93,255,0.4); } .usr-nav-indicator::-webkit-scrollbar { width: 2px; } .usr-nav-indicator::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.08); border-radius: 2px; } .usr-nav-indicator::-webkit-scrollbar-track { background: transparent; } `; document.head.appendChild(style); // ========== 核心逻辑(99% 沿用 7.6) ========== const USER_BUBBLE_SEL = 'div[class*="send-msg-bubble"]'; let indicator = null; let bubbleCache = []; let activeIndex = -1; let items = []; let lastBubbleCount = 0; // 用于轮询判断 function buildNav() { const bubbles = document.querySelectorAll(USER_BUBBLE_SEL); // 用数量判断是否需要重建(可靠而且简单) if (bubbles.length === 0 || bubbles.length === lastBubbleCount) return; lastBubbleCount = bubbles.length; indicator.innerHTML = ''; bubbleCache = Array.from(bubbles); items = []; const fragment = document.createDocumentFragment(); bubbleCache.forEach((bubble, i) => { const item = document.createElement('div'); item.className = 'usr-nav-item'; const marker = document.createElement('span'); marker.className = 'usr-nav-dash-marker'; const text = document.createElement('span'); text.className = 'usr-nav-text'; text.textContent = `${i + 1}. ${bubble.textContent.trim().slice(0, 22)}`; item.appendChild(marker); item.appendChild(text); item.addEventListener('click', (e) => { e.stopPropagation(); bubble.scrollIntoView({ behavior: 'smooth', block: 'center' }); bubble.style.transition = 'box-shadow 0.2s'; bubble.style.boxShadow = '0 0 0 3px rgba(22,93,255,0.4)'; setTimeout(() => bubble.style.boxShadow = '', 1500); }); fragment.appendChild(item); items.push(item); }); indicator.appendChild(fragment); observeBubbles(); updateActiveByScroll(); } const ob = new IntersectionObserver((entries) => { let best = -1, bestRatio = 0; entries.forEach(e => { if (e.isIntersecting && e.intersectionRatio > bestRatio) { bestRatio = e.intersectionRatio; best = bubbleCache.indexOf(e.target); } }); if (best !== -1 && best !== activeIndex) { activeIndex = best; updateActiveClass(); } }, { threshold: [0, 0.2, 0.5] }); function observeBubbles() { ob.disconnect(); bubbleCache.forEach(b => ob.observe(b)); } function updateActiveClass() { items.forEach((item, i) => { item.classList.toggle('active', i === activeIndex); }); if (activeIndex >= 0 && items[activeIndex]) { setTimeout(() => { items[activeIndex].scrollIntoView({ block: "center", behavior: "smooth" }); }, 10); } } function updateActiveByScroll() { let closest = -1; let minDist = Infinity; const viewCenter = window.innerHeight / 2; bubbleCache.forEach((bubble, i) => { const rect = bubble.getBoundingClientRect(); if (rect.height === 0) return; const dist = Math.abs(rect.top + rect.height / 2 - viewCenter); if (dist < minDist) { minDist = dist; closest = i; } }); if (closest !== -1 && closest !== activeIndex) { activeIndex = closest; updateActiveClass(); } } let scrollTimer; window.addEventListener('scroll', () => { clearTimeout(scrollTimer); scrollTimer = setTimeout(updateActiveByScroll, 80); }, { passive: true }); // ========== 轻量轮询 ========== function startPolling() { setInterval(() => { // 页面不可见时跳过,节省资源 if (document.hidden) return; const currentCount = document.querySelectorAll(USER_BUBBLE_SEL).length; if (currentCount !== lastBubbleCount) { buildNav(); } }, 1000); // 每秒检查一次 } async function init() { while (!document.querySelector('.scrollable-Se7zNt')) { await new Promise(r => setTimeout(r, 300)); } const outer = document.createElement('div'); outer.className = 'usr-nav-native'; indicator = document.createElement('div'); indicator.className = 'usr-nav-indicator'; outer.appendChild(indicator); document.body.appendChild(outer); buildNav(); // 首次填充 startPolling(); // 开启定时监测 } window.addEventListener('load', () => setTimeout(init, 400)); })();