- AI 应用
- 前端
【免费下载链接】voyager
Enhancement suite for Gemini, AI Studio, Claude & ChatGPT — plus a prompt manager for any websites, DeepSeek Harness included. / 面向 Gemini、AI Studio、Claude 与 ChatGPT 的增强套件;其中的提示词管理器可用于任意网站,如 DeepSeek Harness。
当你在 Gemini 中向上翻看历史对话、正在阅读旧消息时,只要在输入框按下 Enter 发送新提示,页面就会“强行跳回底部”去追踪新生成的回复,瞬间打断你的阅读节奏。Voyager 的Evitar desplazamiento automático(Prevent Auto Scroll)功能正是为消除这一体验痛点而设计的:它拦截 Gemini 的强制跳底行为,让你停留在当前阅读位置。
本指南以 docs/es/guide/prevent-auto-scroll.md 为骨架,结合仓库中从弹窗设置到页面级脚本的完整源码实现,说明该功能的启用方法、拦截判定规则、底层补丁机制与边界条件,帮助你既会用、也看得懂它是如何做到的。
功能概述:解决什么问题
在使用 Gemini 阅读过往对话时,Gemini 出于“追踪最新响应”的设计,会在新消息发出后把页面强制滚动到底部。若你恰好翻到上方阅读历史内容,这种跳转会破坏阅读连续性。
Voyager 的「Evitar desplazamiento automático」特性拦截这一不受欢迎的跳转行为:
- 当你已向上滚动阅读历史时,系统阻止页面再次向下跳转;
- 该特性默认关闭,需要手动在扩展弹窗的Timeline Options(时间线选项)区域开启。
其核心设计哲学是“只在用户确实向上翻阅历史时才拦截”,既不干扰正常阅读,也不影响切换对话后 Gemini 恢复到最新消息的原生行为。
如何启用
按以下三步即可在 Voyager 弹窗中开启:
- 点击浏览器工具栏中的Voyager 扩展图标,打开扩展弹窗;
- 找到"Timeline Options"(时间线选项)区域;
- 打开"Prevent auto-scroll to bottom"(Evitar desplazamiento automático)开关。
该开关在弹窗设置界面由 TimelineSettingsCard.tsx 渲染,使用SettingToggleRow组件承载,配置项 ID 为preventAutoScroll,并带有辅助说明文本preventAutoScrollHint。在西班牙语语言包 src/locales/es/messages.json 中,开关标题为 "Evitar desplazamiento automático",提示文本为:"Mantiene tu posición al leer turnos anteriores. Gemini aún puede saltar al último mensaje justo después de abrir o cambiar de chat."(保持你在阅读历史轮次时的位置;Gemini 仍然可能在刚打开或切换对话后跳到最后一条消息)。
这一提示文本其实已经预告了该功能最重要的行为边界——下文「初始化与路由恢复窗口」小节会给出源码级解释。
设置持久化与状态同步:存储键与桥接层
弹窗中的开关状态最终写入chrome.storage.sync。核心存储键定义在 src/core/types/common.ts:
| 存储键常量 | 键名 | 用途 |
|---|---|---|
PREVENT_AUTO_SCROLL_ENABLED | gvPreventAutoScrollEnabled | 功能总开关(默认false) |
CTRL_ENTER_SEND | gvCtrlEnterSend | Ctrl+Enter 发送模式的联动开关(默认false) |
之所以需要第二个键,是因为脚本需要区分“Enter 直接发送”与“Ctrl+Enter 发送”两种输入模式,才能正确判定发送意图(详见下文「发送意图识别」)。
内容脚本与桥接元素
入口函数startPreventAutoScroll()位于 src/pages/content/preventAutoScroll/index.ts,并在 src/pages/content/index.tsx 中被调用。它做三件事:
- 创建(或复用)一个隐藏的桥接
<div id="gv-prevent-auto-scroll-bridge">元素; - 从
chrome.storage.sync读取gvPreventAutoScrollEnabled与gvCtrlEnterSend两个布尔值,写入桥接元素的data-enabled与data-ctrl-enter-send属性; - 将页面级脚本
prevent-auto-scroll.js注入页面,并监听chrome.storage.onChanged,在用户于弹窗中切换开关时实时把新状态同步到桥接元素。
技术要点:Voyager 采用“桥接元素”而非消息通道来传递设置,是因为注入到页面世界的脚本无法直接访问
chrome.storage。桥接 DOM 元素成为内容脚本(扩展世界)与页面脚本(页面世界)之间零开销的状态通道。
Safari 的特殊处理
在 Safari 构建中,注入逻辑会提前返回(见 src/pages/content/preventAutoScroll/index.ts):Gemini 的 CSP 会阻止扩展通过<script>标签注入脚本,因此 Safari 版本改由原生 MAIN-world 清单入口加载页面脚本。这也是public/prevent-auto-scroll.js被声明为web_accessible_resources(见 manifest.json)并在 Safari 工程中单独引用的原因。
页面级拦截脚本:拦截判定与补丁机制
真正的拦截逻辑在 public/prevent-auto-scroll.js 中,这是一个以window.__gvPreventAutoScrollInstalled做幂等保护的 IIFE。它围绕三条主线工作。
1. 滚动容器识别
脚本维护两组 CSS 选择器:
CHAT_SCROLL_SELECTOR:会话主滚动区,涵盖#chat-history、infinite-scroller.chat-history、.chat-history-scroll-container、chat-window、.conversation-container等;SIDEBAR_SELECTOR:侧边栏区域(bard-sidenav、[data-test-id="overflow-container"]、.gv-folder-container等)。
isChatScrollElement()判定一个元素是否属于会话滚动区,且明确排除侧边栏——这意味着侧边栏的会话列表滚动永远不受拦截(测试用例allows the sidebar history list to scroll after a submit验证了这一点)。
2. “已向上翻阅”判定
isScrolledUp()的判断条件是:
if (sh <= ch + 10) return false; // 不可滚动或内容极短时不拦截 return sh - st - ch > 150; // 距底部超过 150px 视为“正在阅读历史”其中sh、st、ch分别是滚动高度、当前滚动位置、可视区高度。只有当用户距底部超过 150 像素时才认为处于“阅读历史”状态,拦截向下滚动;贴底观看新回复时则放行,避免影响正常体验。
3. 多路径的滚动 API 补丁
为覆盖 Gemini 可能使用的所有滚动途径,脚本逐一补丁了以下 API:
window.scrollTo/window.scrollBy;Element.prototype.scrollTo/scrollBy;Element.prototype.scrollIntoView;Element.prototype.scrollTop的 setter(仅在scrollTop描述符可配置时改写,并完整保留原描述符的configurable/enumerable标志)。
每种补丁都复用同一套判定逻辑:shouldBlockAutoScroll()(功能已启用、且超出恢复窗口)+isChatScrollElement()(属于会话滚动区)+isScrolledUp()(已翻阅历史)。scrollTo/scrollBy还额外用isScrollingDownTo/isScrollingDownBy确认目标方向确实是向下。
scrollIntoView的处理最为精细:当判定需要拦截时,脚本先通过collectVerticalScrollPositions()记录当前所有可滚动祖先与窗口的滚动位置,然后调用原始scrollIntoView让 Gemini 的副作用照常执行,最后在finally中调用restoreVerticalScrollPositions()把滚动位置还原——既“让了路”又“守住了位置”。
关键时序窗口:何时允许、何时拦截
脚本用三个时间常量精确控制“允许原生滚动”的窗口,这是避免误拦截的关键:
| 常量 | 时长 | 语义 |
|---|---|---|
INITIAL_NATIVE_SCROLL_ALLOW_MS | 8000ms | 页面初次加载后,允许 Gemini 恢复到最新消息(issue #741) |
ROUTE_NATIVE_SCROLL_ALLOW_MS | 4000ms | 路由切换(打开/切换对话)后短暂恢复原生滚动 |
SUBMIT_ROUTE_GRACE_MS | 5000ms | 提交后的一段宽限期,避免路由驱动的恢复干扰“Enter 阅读中发送”的拦截 |
配套逻辑包括:
- 通过
wrapHistoryMethod('pushState'/'replaceState')、popstate、hashchange监听路由变化,在 URL 变化时调用allowNativeScrollFor(ROUTE_NATIVE_SCROLL_ALLOW_MS)重新开放窗口; handleUserScrollIntent()监听捕获阶段的wheel与touchmove:只要用户在会话区域(非侧边栏)滚动,就立即cancelNativeScrollAllow(),掐断任何尚未结束的恢复窗口——这正是弹窗提示文案所说“保持位置”的底层来源。
发送意图识别:精确区分“用户要发送”
拦截是否生效,很大程度上取决于能否准确识别“用户刚刚提交了消息”。脚本从两个维度捕获发送意图:
- 键盘:
keydown捕获阶段监听 Enter(排除 Shift/Alt 组合与输入法组合态isComposing)。若启用了 Ctrl+Enter 发送模式,则仅当按下 Ctrl/Meta+Enter 时才算提交; - 鼠标:
click捕获阶段用isLikelySendButton()识别发送按钮,判断依据包括aria-label、title、data-tooltip、data-test-id、文本内容(内置多语言正则,覆盖send/submit/run/update及中、日、韩、西、法、德、俄、阿拉伯语等关键词),以及mat-icon/ Material 图标名是否为send或play_arrow。
一旦判定为提交,markSubmitIntent()会立即将nativeScrollAllowedUntil清零并记录lastSubmitIntentAt——即使此刻仍处于某个恢复窗口内,也立刻进入拦截态,保证“翻到上面、按下 Enter、页面不跳走”。
测试验证:行为契约的可信依据
该功能配有完整的 Vitest 测试套件 preventAutoScrollScript.test.ts,覆盖了上述全部关键行为,可作为阅读源码时的行为契约参考:
- 初始加载 8 秒窗口内放行
scrollTo({ top: 1800 }),窗口过期后拦截; - 用户在恢复窗口内滚动(
wheel)后,立即取消恢复窗口并拦截; - 从 composer 提交(Enter / Ctrl+Enter / 发送按钮点击)后,向下滚动一律被拦截;
contenteditable="plaintext-only"输入区同样识别为提交目标;- 启用 Ctrl+Enter 发送时,普通 Enter 不再被视为提交、不做拦截;
- 侧边栏滚动不受影响;
scrollIntoView的原生副作用照常执行,但会话滚动位置被还原(getScrollTop()保持 0);scrollTop原型补丁保留原始描述符标志;- body/html 作为滚动根时视口位置同样被还原;
- 提交超过 6 秒后发生路由切换,重新允许原生滚动。
边界条件与注意事项
- 默认关闭:功能默认不启用,需在弹窗「Timeline Options」中手动打开;
- 恢复窗口:刚打开页面或切换对话后的短暂时间内,Gemini 仍会跳到最新消息,这是有意为之(
INITIAL_NATIVE_SCROLL_ALLOW_MS/ROUTE_NATIVE_SCROLL_ALLOW_MS),对应弹窗提示文案中的说明; - 只拦“向下”:判断基于距底距离与目标方向,用户主动向下滚动(如快速回到底部)不受限制;
- 平台差异:Chrome/Firefox 构建走
<script>注入,Safari 构建因 Gemini CSP 限制改走原生 MAIN-world 清单条目,代码路径不同但行为一致; - 仅适用于会话滚动区:侧边栏会话列表的滚动永远不受该功能影响。
至此,你不仅知道了在哪里打开开关,也理解了 Voyager 是如何通过「桥接元素 + 页面级 API 补丁 + 时序窗口 + 发送意图识别」四层机制,在不破坏 Gemini 原生行为的前提下,精准守护你的阅读位置。
- AI 应用
- 前端
【免费下载链接】voyager
Enhancement suite for Gemini, AI Studio, Claude & ChatGPT — plus a prompt manager for any websites, DeepSeek Harness included. / 面向 Gemini、AI Studio、Claude 与 ChatGPT 的增强套件;其中的提示词管理器可用于任意网站,如 DeepSeek Harness。
相关推荐
.swf文件打不开?用Ruffle桌面版3种方式快速播放的完整指南
.swf文件打不开?用Ruffle桌面版3种方式快速播放的完整指南 手里攥着一堆 .swf 文件,双击却什么都打不开——Flash Player 早从浏览器里消
AI 应用前端如何让Vue聊天界面自动滚动到底部?超简单的终极实现方案
如何让Vue聊天界面自动滚动到底部?超简单的终极实现方案 在开发聊天应用或实时日志系统时,你是否遇到过新消息出现后用户需要手动滚动到底部的尴尬情况?Vue Ch
Alpine.js自动滚动:实现聊天窗口自动到底部
Alpine.js自动滚动:实现聊天窗口自动到底部 在实时聊天应用中,当新消息到来时自动滚动到最新内容是提升用户体验的关键功能。Alpine.js作为轻量级前端
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考