Voyager「Evitar desplazamiento automático」:彻底解析 Gemini 聊天界面自动滚动拦截机制的实现原理与启用方法
2026/9/20 10:55:56 网站建设 项目流程
  • 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。

项目地址:https://gitcode.com/gh_mirrors/ge/voyager
点击查看免费下载

当你在 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 弹窗中开启:

  1. 点击浏览器工具栏中的Voyager 扩展图标,打开扩展弹窗;
  2. 找到"Timeline Options"(时间线选项)区域;
  3. 打开"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_ENABLEDgvPreventAutoScrollEnabled功能总开关(默认false
CTRL_ENTER_SENDgvCtrlEnterSendCtrl+Enter 发送模式的联动开关(默认false

之所以需要第二个键,是因为脚本需要区分“Enter 直接发送”与“Ctrl+Enter 发送”两种输入模式,才能正确判定发送意图(详见下文「发送意图识别」)。

内容脚本与桥接元素

入口函数startPreventAutoScroll()位于 src/pages/content/preventAutoScroll/index.ts,并在 src/pages/content/index.tsx 中被调用。它做三件事:

  1. 创建(或复用)一个隐藏的桥接<div id="gv-prevent-auto-scroll-bridge">元素;
  2. chrome.storage.sync读取gvPreventAutoScrollEnabledgvCtrlEnterSend两个布尔值,写入桥接元素的data-enableddata-ctrl-enter-send属性;
  3. 将页面级脚本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-historyinfinite-scroller.chat-history.chat-history-scroll-containerchat-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 视为“正在阅读历史”

其中shstch分别是滚动高度、当前滚动位置、可视区高度。只有当用户距底部超过 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_MS8000ms页面初次加载后,允许 Gemini 恢复到最新消息(issue #741)
ROUTE_NATIVE_SCROLL_ALLOW_MS4000ms路由切换(打开/切换对话)后短暂恢复原生滚动
SUBMIT_ROUTE_GRACE_MS5000ms提交后的一段宽限期,避免路由驱动的恢复干扰“Enter 阅读中发送”的拦截

配套逻辑包括:

  • 通过wrapHistoryMethod('pushState'/'replaceState')popstatehashchange监听路由变化,在 URL 变化时调用allowNativeScrollFor(ROUTE_NATIVE_SCROLL_ALLOW_MS)重新开放窗口;
  • handleUserScrollIntent()监听捕获阶段的wheeltouchmove:只要用户在会话区域(非侧边栏)滚动,就立即cancelNativeScrollAllow(),掐断任何尚未结束的恢复窗口——这正是弹窗提示文案所说“保持位置”的底层来源。

发送意图识别:精确区分“用户要发送”

拦截是否生效,很大程度上取决于能否准确识别“用户刚刚提交了消息”。脚本从两个维度捕获发送意图:

  • 键盘keydown捕获阶段监听 Enter(排除 Shift/Alt 组合与输入法组合态isComposing)。若启用了 Ctrl+Enter 发送模式,则仅当按下 Ctrl/Meta+Enter 时才算提交;
  • 鼠标click捕获阶段用isLikelySendButton()识别发送按钮,判断依据包括aria-labeltitledata-tooltipdata-test-id、文本内容(内置多语言正则,覆盖send/submit/run/update及中、日、韩、西、法、德、俄、阿拉伯语等关键词),以及mat-icon/ Material 图标名是否为sendplay_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。

项目地址:https://gitcode.com/gh_mirrors/ge/voyager
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询