「回到底部」胶囊改为基于列表可见底边定位
- styles.css 把消息列表改成 flex 列容器并显式 gap: 0——原 gap: 14px 对块级容器无效,改成 flex 后会与子项的 margin 叠成双倍间距 - 列表子项 flex: 0 0 auto 防止内容溢出一屏时被纵向压缩,列表仍是唯一滚动区 - 胶囊 margin-top: auto:内容不足一屏时吃掉剩余空间顶到可见底边,内容溢出一屏时归零、交由 sticky bottom-3 上拉贴底 - DirectProjectScrollToBottomCapsule 增加稳定类名 project-chat-scroll-to-bottom、去掉 mt-1,并更新组件说明 - ADR 第 3 节与备选方案 2 同步新的定位方式
This commit is contained in:
@@ -13401,10 +13401,11 @@ button.design-workspace-tree__entry:hover,
|
||||
grid-row: 1 / span 2;
|
||||
}
|
||||
|
||||
/* 消息之间的间距:消息列表是块级滚动容器(不是 flex/grid),基础规则里的 `gap: 14px`
|
||||
对它无效,而被改写成 `margin-top: 0` 的 `.message + .message` 又让相邻消息贴在一起。
|
||||
这里给列表的相邻子元素统一加间距(消息、工具调用折叠块、思考过程块都适用),
|
||||
间距值与消息内部的行距观感一致。 */
|
||||
/* 消息之间的间距:消息列表是块级滚动流,基础规则里的 `gap: 14px` 对它无效,而被改写成
|
||||
`margin-top: 0` 的 `.message + .message` 又让相邻消息贴在一起。这里给列表的相邻子元素统一
|
||||
加间距(消息、工具调用折叠块、思考过程块都适用),间距值与消息内部的行距观感一致。
|
||||
(列表下面被改成 flex 列容器,只是为了给「回到底部」胶囊一个 `margin-top: auto` 的落点;
|
||||
flex 里 margin 不折叠,这条间距规则仍照常生效。) */
|
||||
.game-workbench-chat
|
||||
.project-chat-surface.is-direct-codex
|
||||
.project-chat-message-list
|
||||
@@ -13413,6 +13414,37 @@ button.design-workspace-tree__entry:hover,
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
/* 「回到底部」胶囊的位置真源:把消息列表做成 **flex 列容器**,胶囊是最后一个 flex 项。
|
||||
- 内容不足一屏:`margin-top: auto` 吃掉剩余空间,把胶囊顶到列表可见底边;
|
||||
- 内容溢出一屏:auto 归零,胶囊落回内容末尾,再由自身的 `sticky bottom-3` 上拉贴底。
|
||||
只有 sticky 是不够的:sticky 只能把元素**上拉**,不能下推——内容不足一屏时胶囊会停在
|
||||
文档流里(表现为悬空,见 `docs/adr/【ADR】DirectProject对话滚动与历史自动加载-2026-10-02.md`)。
|
||||
`gap: 0` 必须显式写:上面 `.… .project-chat-message-list { gap: 14px }` 是给 flex 预备的,
|
||||
改成 flex 后它会生效,与下面那些 `> * + *` 的 margin 叠成双倍间距。
|
||||
`> * { flex: 0 0 auto }` 保证内容溢出一屏时子项不被纵向压缩,列表仍是唯一滚动区;
|
||||
子项间距仍由上面那条 `> * + *` 给(flex 里 margin 不折叠,差值与块级最多 2px)。 */
|
||||
.game-workbench-chat
|
||||
.project-chat-surface.is-direct-codex
|
||||
.project-chat-message-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.game-workbench-chat
|
||||
.project-chat-surface.is-direct-codex
|
||||
.project-chat-message-list
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.game-workbench-chat
|
||||
.project-chat-surface.is-direct-codex
|
||||
.project-chat-message-list
|
||||
> .project-chat-scroll-to-bottom {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* 发送时间与完成后的过程区沿用对话面板的次要信息样式。 */
|
||||
.message-sent-at {
|
||||
display: block;
|
||||
|
||||
+12
-4
@@ -3,9 +3,17 @@ import { ArrowDown } from 'lucide-react';
|
||||
/**
|
||||
* 底部居中的「回到底部」胶囊。
|
||||
*
|
||||
* 用 `sticky bottom-*` 而不是浮层:工作台里 `.project-chat-conversation` 是 `display: block`
|
||||
* 加 `height: 100%` 的几何,在列表外套一层定位容器会把列表的 `height: 100%` 塌成内容高度;
|
||||
* 粘在列表内部的胶囊零结构改动,两块宿主(工作台侧栏与独立页面)都能拿到。
|
||||
* 它是消息列表的**最后一个 flex 项**,贴底靠两件事配合(见 `styles.css` 里
|
||||
* `.project-chat-scroll-to-bottom` 那组规则):
|
||||
* - `margin-top: auto`:内容不足一屏时吃掉剩余空间,把胶囊顶到列表可见底边;
|
||||
* - `sticky bottom-3`:内容溢出一屏时把胶囊从内容末尾**上拉**贴住可见底边。
|
||||
* 只用 `sticky` 不够——它只能上拉、不能下推,内容不足一屏时元素会停在文档流里(悬空)。
|
||||
*
|
||||
* 用列表内的 flex 项而不是浮层:工作台里 `.project-chat-surface.is-direct-codex >
|
||||
* .project-chat-conversation` 是纵向 flex,`.project-chat-message-list` 是其中
|
||||
* `flex: 1 1 auto` 的唯一滚动项;在列表外套一层定位容器会让列表塌成内容高度,
|
||||
* 改公共类又会连带 `PlanningChatView`。粘在列表内部零结构改动,两块宿主
|
||||
* (工作台侧栏与导出面板)都能拿到。
|
||||
*
|
||||
* 显隐与文案由调用方决定(距底超过阈值才出现;不跟随时来了新回复就换文案),这里只负责表现。
|
||||
*/
|
||||
@@ -19,7 +27,7 @@ export function DirectProjectScrollToBottomCapsule({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="sticky bottom-3 z-10 mx-auto mt-1 flex w-fit items-center gap-1.5 rounded-full border border-[color:var(--platform-subpanel-border)] bg-[var(--platform-neutral-bg)] px-3 py-1.5 text-xs font-medium text-[color:var(--platform-text-strong)] shadow-[0_6px_18px_rgba(24,32,47,0.16)] backdrop-blur"
|
||||
className="project-chat-scroll-to-bottom sticky bottom-3 z-10 mx-auto flex w-fit items-center gap-1.5 rounded-full border border-[color:var(--platform-subpanel-border)] bg-[var(--platform-neutral-bg)] px-3 py-1.5 text-xs font-medium text-[color:var(--platform-text-strong)] shadow-[0_6px_18px_rgba(24,32,47,0.16)] backdrop-blur"
|
||||
onClick={onClick}
|
||||
>
|
||||
<ArrowDown size={13} aria-hidden="true" />
|
||||
|
||||
@@ -38,7 +38,7 @@ DirectProject 聊天区(`apps/ai-game-creator-shell/src/view/project-developme
|
||||
|
||||
### 3. 回到底部胶囊
|
||||
|
||||
- 列表底部居中的悬浮胶囊(`sticky`),跟着滚动容器走、不随内容滚走。
|
||||
- 列表底部居中的悬浮胶囊,**位置基于列表可见底边**:列表是 flex 列容器,胶囊是最后一个 flex 项,`margin-top: auto` 在内容不足一屏时把它顶到可见底边,内容溢出一屏时 auto 归零、再由 `sticky bottom-3` 上拉贴底。只用 `sticky` 不够——它只能把元素**上拉**、不能下推,内容不足一屏时胶囊会停在文档流里(悬空),所以必须由 flex 的 auto margin 兜住「不足一屏」这一半。
|
||||
- 距底部超过 48px 时出现,文案「回到底部」;用户不跟随时来了新的终态内容就改成「有新回复 · 回到底部」。
|
||||
- 贴底判定不能只认 `onScroll`:内容**变短**到一屏以内时不会再有任何滚动事件(回合收口把过程折进收起的 `<details>`、历史加载行卸载),`atBottom` 会停在离开底部时的假值,胶囊就永远挂在一个滚不动的列表上(短历史一屏显示完却还悬着「回到底部」)。因此每次布局变化(`ResizeObserver`)、子元素增删(`MutationObserver`)与内容变化后都按真实几何复核一次 `isNearBottom`,复核为真即收起胶囊并清掉「有新回复」。
|
||||
- 点击:平滑滚到底部 + 恢复跟随最新 + 清除「有新回复」,随后按钮自行消失。
|
||||
@@ -69,7 +69,7 @@ DirectProject 聊天区(`apps/ai-game-creator-shell/src/view/project-developme
|
||||
## 备选方案与取舍
|
||||
|
||||
1. **保留按钮 + 只加自动加载**:加载中仍靠按钮做唯一反馈,且删掉按钮后失败路径没有补救入口;按钮本身与滚动自动加载重复。
|
||||
2. **在列表外面套一层 viewport 做浮层定位**:`PlanningChatView` 共用同一套容器规则,且工作台里 `.project-chat-conversation` 是 `display: block` + `height: 100%` 几何,套一层就会让 `height: 100%` 的列表塌成内容高度;改公共类会连带策划对话。改用列表内的 `sticky` 胶囊,零结构改动。
|
||||
2. **在列表外面套一层 viewport 做浮层定位**:`PlanningChatView` 共用同一套容器规则,且工作台里 `.project-chat-surface.is-direct-codex > .project-chat-conversation` 是纵向 flex、`.project-chat-message-list` 是其中 `flex: 1 1 auto` 的唯一滚动项,套一层定位容器会让列表塌成内容高度;改公共类会连带策划对话。改用列表内的 flex 项(`margin-top: auto` + `sticky`),零结构改动。
|
||||
3. **只依赖原生 CSS scroll anchoring**:前插能免费对齐,但做不到「跟随时展开要贴底」,也无法在加载期间冻结同一套锚点;因此显式补偿 + 关闭原生锚定。
|
||||
4. **展开后总是把正文滚进视口**:对正文比视口矮的折叠块会把画面大幅上移,打断正在读历史的用户;采用「跟随时贴底 / 否则冻结折叠头 + 只滚到刚好露出新展开正文的最小位移」。
|
||||
5. **平滑滚动期间照常处理滚动事件与布局补偿**:程序化滚动会被应用自己的补偿打断(第一次写 `scrollTop` 即取消动画),用户点了「回到底部」也停在半路;因此改为滚动期间冻结这两条路径。
|
||||
|
||||
Reference in New Issue
Block a user