// ==UserScript== // @name DeepSeek一键回顶 // @namespace https://scriptcat.org/zh-CN/users/214553 // @version 1.0.0 // @description 在DeepSeek网页上添加「回到顶部」按钮,一键回到对话的最开始。 // @author English3(Σπg|!εh๑๑๑) // @license MIT // @icon https://deepseek.com/favicon.ico // @match https://chat.deepseek.com/* // @run-at document-idle // @grant none // ==/UserScript== (() => { 'use strict'; const ID = 'ds-back-top'; // 回顶:找到滚得最多的元素,直接归零 const goTop = () => { let best = null, max = 0; for (const el of document.querySelectorAll('*')) { if (el.scrollTop > max) { max = el.scrollTop; best = el; } } if (best) best.scrollTop = 0; try { document.documentElement.scrollTop = 0; document.body.scrollTop = 0; window.scrollTo(0, 0); } catch (_) {} }; // 按钮创建 const btn = document.createElement('button'); btn.id = ID; btn.type = 'button'; btn.title = '回到顶部'; btn.setAttribute('aria-label', '回到顶部'); btn.innerHTML = ` `; Object.assign(btn.style, { background: 'transparent', border: 'none', borderRadius: '8px', width: '32px', height: '32px', padding: '0', margin: '0 2px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: 'inherit', opacity: '0.7', flexShrink: '0', transition: 'opacity .15s, background .15s', }); btn.onmouseenter = () => { btn.style.opacity = '1'; btn.style.background = 'rgba(128,128,128,0.15)'; }; btn.onmouseleave = () => { btn.style.opacity = '0.7'; btn.style.background = 'transparent'; }; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); goTop(); }); // 寻找顶部标题栏 const findHeader = () => { const W = window.innerWidth; const list = []; for (const el of document.querySelectorAll('div, header, nav, section')) { const r = el.getBoundingClientRect(); if (r.top > 30 || r.height > 90 || r.height < 24) continue; if (r.width < W * 0.6) continue; const rightBtns = [...el.querySelectorAll('button, [role="button"]')] .filter(b => { const br = b.getBoundingClientRect(); return br.left > W * 0.5 && br.width > 0 && br.height > 0; }); if (rightBtns.length >= 2) list.push({ el, n: rightBtns.length }); } list.sort((a, b) => b.n - a.n); return list[0]?.el || null; }; // 插入按钮(修复:只在位置不对时才插入) const insertIntoHeader = (header) => { const W = window.innerWidth; const rightBtns = [...header.querySelectorAll('button, [role="button"]')] .filter(b => { const r = b.getBoundingClientRect(); return r.left > W * 0.5 && r.width > 0 && r.height > 0 && b.id !== ID; }) .sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left); if (!rightBtns.length) return false; const first = rightBtns[0]; const parent = first.parentElement; if (!parent) return false; // 【核心修复点】:如果已经在目标位置,绝不重复调用 insertBefore,防止引发重绘和闪烁 if (btn.parentElement !== parent || btn.nextElementSibling !== first) { parent.insertBefore(btn, first); } return true; }; // 状态管理 let isFloating = false; // 记录当前是否处于浮动兜底状态 // 主循环 const ensure = () => { const header = findHeader(); // 尝试插入顶部栏 if (header && insertIntoHeader(header)) { // 从浮动状态切换到标题栏,重置样式(只执行一次) if (isFloating) { isFloating = false; Object.assign(btn.style, { position: '', right: '', bottom: '', width: '32px', height: '32px', borderRadius: '8px', background: 'transparent', color: 'inherit', zIndex: '', boxShadow: '' }); } return; // 成功放入顶部栏,结束本次循环 } // 兜底浮动逻辑(找不到标题栏时) if (!isFloating) { isFloating = true; Object.assign(btn.style, { position: 'fixed', right: '20px', bottom: '170px', width: '42px', height: '42px', borderRadius: '50%', background: 'rgba(0,0,0,0.55)', color: '#fff', zIndex: '2147483647', boxShadow: '0 2px 10px rgba(0,0,0,0.3)', }); } // 如果被React移出了DOM,重新挂载到body if (!btn.isConnected) { document.body.appendChild(btn); } }; // 降低频率,减少性能消耗。700ms可以放宽到1000ms,因为加入了防抖判断 setInterval(ensure, 1000); ensure(); setTimeout(ensure, 200); setTimeout(ensure, 1000); })();