Co-Authored-By: Claude Code <noreply@anthropic.com>
32 KiB
UI-04:嵌入组件自动上滚照片窗高保真实施方案
版本:1.0
日期:2026-09-22
状态:待执行;任务复选框与验收项均须实现后填写。
执行对象:Claude 或其他接手本仓库的开发者。
0. 执行目标
将新版嵌入组件实现为:PC和手机均195×280 CSS px、照片连续自动向上滚动、真实点赞心形气泡、无标题栏和底部数量/操作栏,点击照片进入对应观众页,仅保留照片右上收起入口。
这不是旧轮播改颜色任务。必须改变展示模型:从单张横向/淡入轮播改成单列连续照片带。交付真实可运行的loader、iframe、相关配置/预览与测试证据,不能仅制作静态页面或另写方案。
唯一目标图
- 最新设计说明
- 旧的大卡片、固定小卡片带底栏两版图不再是本次目标。
- UI-01中组件的通用安全、权限和生命周期要求继续有效;展示样式以本文为准。
- 图上宿主网站、尺寸箭头、外部英文说明是展示板内容,不得注入用户网站。
- 画板中的PC/手机窗口缩放不同,实际尺寸以浏览器getBoundingClientRect测量为准,不能从画板像素反推。
- 图是静态中途滚动示意;完整验收必须含动态视频与状态测试。
1. 当前实现与改造边界
1.1 已核对的现状
packages/embed-loader/src/params.js当前sm=280、md=320。- 已有脚本显式参数 > 后台配置 > 默认值的优先级,configVersion校验、collapse/expand/config/destroy消息。
- loader已有宿主定位、多实例排列、收起记忆与窗口适配。
EmbedPage.vue是数据/生命周期薄壳;现有pages/embed/展示组件包含轮播舞台与底栏。useEmbedFeed.ts已有取数、退避、可见性、停用处理,优先复用。- 观众页已有
RealtimeClient及点赞事件处理,但不能假设嵌入组件已经有独立实时订阅。 - 现有布局/配置不是本效果图,不能仅把SIZE_WIDTH改195就宣布完成。
1.2 本轮范围
必须完成:新展示模式、尺寸规则、无缝向上滚动、真实气泡、点击/暂停/收起、数据更新一致性、后台预览/接入代码、版本兼容、视觉及动态验收。
不包含:重做宿主网站、App、普通观众页、后端全部同步系统;新增嵌入组件点赞提交按钮;登录/注册、评论、统计看板、原图下载、自动弹新页。
保护当前工作树改动,不执行reset/clean;不自动推送、部署、打发布标签或修改CDN。外部资源不足要登记,继续可完成任务,但不得把关键视觉/动态项标通过。
2. 固定视觉规格
2.1 尺寸与定位(硬性要求)
| 属性 | 新版固定值 |
|---|---|
| 宽 | 195 CSS px |
| 高 | 280 CSS px(包含边框) |
| box-sizing | border-box |
| 桌面位置 | 默认right:16px,bottom:24px + safe-area |
| 手机位置 | 默认right:12px,bottom:24px + safe-area |
| 圆角 | 12px |
| 边框 | 1px rgba(255,255,255,0.80),不叠两圈边框 |
| 阴影 | 0 4px 16px rgba(15,23,42,0.16) |
| 背景 | #10161E,加载/缺口不露宿主内容 |
| z-index | 默认1000,沿用显式可配置范围 |
| 外部留白 | 0,照片铺满内部舞台 |
- PC不能使用50vw/33vh,手机也不是随任意屏幕比例无限伸缩。以390×844手机的半宽/约三分之一高为设计来源,但生产两端固定195×280。
- 320、390、430、768、1440px常见视口都保持相同CSS尺寸。
- 可用宽/高不足时才缩小:W=min(195,可用宽),H=min(280,可用高)。仍需保持有效收起触控;小到无法操作时改成收起入口,而不是缩成不可点。
- 不用transform:scale整体缩放卡片(会同时缩小文字/触控区)。
- wrapper与iframe都使用固定height,iframe内容不能通过旧resize消息把新版改回内容高度。
- 默认手机仍沿用已有“首次收起”策略以减少遮挡,展开后尺寸必须一致;视觉验收要主动展开,不得拿收起截图替代。
- 用户主动展开/收起优先于默认值,不因轮询、换向或配置刷新反复弹出。
- 自定义position/offset/zIndex继续支持,显式值优先;默认12/16只有未配置时按视口取值。
2.2 层级与DOM结构
host fixed wrapper (195×280, clip radius)
iframe (100%×100%, no border)
PhotoStreamViewport (relative, 100%, overflow hidden / manual mode scroll)
Track (vertical photo instances, translateY)
PhotoLink > img
3px separator
PhotoLink > img
...
HeartParticleLayer (absolute, clipped, pointer-events:none)
CollapseButton (absolute top-right, accessible)
StateOverlay (loading/empty/error when needed)
- 没有header、相册标题、footer、页码、Pause按钮、左右箭头、圆点和View链接。
- 不留已删除工具栏的padding/空白高度。
- 关闭控件层级最高,心形层其次,照片层最低;组件外不拦截宿主点击。
- 控件/气泡裁切在窗口内,阴影可以在wrapper外;无需模糊大块背景。
- 图片间隙3px,颜色#F6F8FB。不要每张图再加圆角,外窗负责整体圆角。
2.3 图片呈现
- 内容宽为实际内宽(195减边框);依据元数据宽高按原比例计算图片高度,使用block、width:100%、height:auto。
- 每张照片不固定为195×280;窗口剪切的是滚动照片带,不是把每张图强制裁到窗口比例。
- 真实横图会较矮、竖图较高;图上同时出现2–3个照片片段是示例,生产不强制每时刻恰好三张。
- 超长图默认保持比例;如当前数据存在异常宽高,使用安全占位比例并记录,不让无穷高度破坏循环。
- 确定元数据可用范围和fallback(建议宽高>0且finite;异常值按4:3占位),实际解码与元数据方向不符时受控修正高度并锚定当前位置。
- 只用thumb或适合该宽度的浏览变体;禁止预加载所有原图。
- 可选上下12px弱暗渐隐,opacity≤0.12;不得用强白雾遮掉照片内容。
- 图片内容靠真实API;测试使用固定摄影素材,不能用sharp色块证明摄影视觉还原。
2.4 收起入口
- 可视按钮:右上6px,圆形24×24,背景rgba(255,255,255,0.82),minus图标12,颜色#647084。
- 触控区域44×44,锚定右上;可视圆居于点击区右上内缩6px。这个区域不同时触发照片链接。
- 明确aria-label“收起照片窗 / Collapse photo window”,不是永久关闭展示位。
- 收起后沿用小型Photos/照片入口,建议88–104×44,圆角22、白底、蓝图标16与12px文字。
- 图中没有展示收起样式,沿用此前轻量入口,不能变成195×280白空框。
- 停用/解绑的destroy与用户collapse不同:前者移除照片窗及入口,不允许打开空壳。
3. 自动向上滚动:确定性算法
3.1 速度和时序
| 参数 | 默认值 |
|---|---|
| scrollSpeed | 16 CSS px/秒 |
| 可配置范围 | 8–32 CSS px/秒 |
| 帧驱动 | requestAnimationFrame |
| 启动 | 首个可见区域与其下一张已decode,初始至少静置500ms |
| 首帧等待上限 | 取数/加载失败按状态机,不永久骨架 |
| 手动交互恢复 | 指针/焦点离开后500ms,且没有其他暂停原因 |
| deltaTime | 恢复时重置lastTime;单帧上限50ms,长停顿不补跑 |
保持匀速linear,不要“一张停留5秒→切换”也不要ease-in-out反复加减速。每帧通过elapsed time积分,不能固定每帧移动1px,否则高刷新率设备速度不同。
不在每帧触发整个Vue树更新:用DOM transform/animation state精简更新;测量尺寸在布局事件和队列变更时进行,避免每帧读layout后写style。
3.2 有限环形队列(建议实现)
数据层按photoId保存唯一照片队列;渲染层是有限窗口的实例,不是无限追加DOM。
- 计算照片高度及3px间距,装入足够覆盖窗口和预加载区域的条目。
- 保存头实例ID、头部被移出像素offset及sequence游标。
- rAF增加offset,track translateY(-offset)。
- offset超过头条目高度+gap时,将头条目退役,offset减去该值,并在尾部补下一张;跨界过程应保持屏幕内容相同。
- render实例key包含循环代次,避免photoId重复出现造成重复key。
- 只保留可见窗口+前后缓冲。建议最多16个DOM图片实例、数据队列最多30张;尺寸极矮照片无法填满窗口时使用受控补位或静态回退,不能无限扩DOM。
- 内容两张以上但一轮高度短于窗口,可使用少量可见循环副本填充;副本不要全部可聚焦。相同photoId的非主要副本对辅助技术隐藏,并处理点击到对应原图。
- 仅一张照片时静态展示,不把同一张复制成刷屏。单张长图在减少动画/手动模式下可滚动查看;自动状态优先完整contain单张,不上下无穷重复。
- 无照片使用空态,不启动rAF。
若采用不同循环实现,必须满足相同无跳变、有限DOM、唯一事件绑定、焦点安全和动态更新要求,在验收记录解释;不能用整条CSS动画固定时长后瞬移回原点且露出空白。
3.3 图像预加载和失败
- 启动前固定metadata比例占位,decode成功再显示,避免高度由0膨胀。
- 预加载当前可见项及接下来2张;复用浏览器缓存,不对每个循环重复刷新URL。
- 单张失败:受控刷新签名一次;仍失败则在安全边界跳过该照片,后续刷新成功可恢复。
- 全部失败:停止滚动,紧凑“暂时无法加载照片”状态,保留收起;后台按既有退避继续尝试。
- 首屏网络失败不能永远显示骨架。
- 304不代表签名永久有效;继续复用/修正既有签名刷新策略,重试需有次数和速率上限。
4. 连续滚动下的交互和可访问性
用户要求无底部按钮,不等于去掉暂停/可访问机制。
4.1 照片点击
- 照片用真实a链接,目标为观众
/a/:albumToken/p/:photoId?lang=...。 - 用户手势新标签打开,保留noopener/noreferrer;不打开OSS URL,不自动弹窗。
- pointerdown时暂停并锁定当前目标photoId;pointerup确认未拖动且目标未被删除后才允许点击。
- 位移超过8px或被识别为滚动手势时不触发打开。
- 点击到循环副本仍定位同一照片,不能按当前队列索引重新计算。
- 照片隐藏/换绑后取消待触发点击,防止已下线图仍被打开。
4.2 暂停原因集合
使用Set或等价状态管理以下原因,不能多个布尔值互相误覆盖: hiddenDocument、parentCollapsed、offscreen、hoverFinePointer、focusWithin、pointerActive、manualScroll、reducedMotion、destroyed、loading、insufficientContent。
所有原因清空才可自动滚动。hover仅对支持hover的精细指针启用;触摸手机不能因粘滞hover永久停住。
- 细指针悬停暂停,离开恢复。
- 键盘焦点进入暂停;离开恢复。Space在容器聚焦时切换暂停/恢复,Escape收起;不抢输入字段或宿主按键。
- 减少动态效果时默认禁自动滚动与粒子,允许手指/滚轮/键盘手动查看,不能仅停止在一张不可浏览的裁片。
- 用户手动暂停需有内部状态及aria状态说明,无需额外底部按钮;可以在聚焦时显示非布局占位的小提示,不能常驻抢视觉。
- 普通模式触摸垂直手势进入手动滚动,保留清晰的恢复规则(离开后500ms;用户明确键盘暂停则不自动恢复)。
- 装饰粒子aria-hidden,不让屏幕阅读器逐颗播报。
- 照片链接可访问名称使用真实上下文或“活动照片”,不编造画面描述。
- 不用外层div假装按钮而缺少键盘操作;循环复制内容不可制造无穷Tab顺序。
4.3 手动模式与自动模式转换
切换时用当前photoId + 图内偏移作为锚点。不能将transform的自动视图直接切成scrollTop=0导致回顶。手动模式保持有限真实列表并可滚动;自动恢复时从可见锚点恢复,不重放所有历史。 图内图片的交互与收起入口都必须可用,不能用全窗透明div拦截所有点击实现暂停。
5. 点赞心形气泡
5.1 视觉冻结值
| 项目 | 值 |
|---|---|
| 区域 | 右侧宽54px,上避让收起44px |
| 颜色 | #FF6B7A、#FF8B98,两档即可 |
| 形状 | 同一实心heart,可有微弱白色中心高光;非聊天气泡 |
| 大小 | 12、16、20px |
| 透明度 | 从0→0.85→0,尾部淡出 |
| 生命周期 | 1400–1800ms |
| 上移距离 | 90–130px |
| 左右漂移 | -8..8px的预定义曲线 |
| 旋转 | -8..8度 |
| 同时在屏数量 | 最大8 |
| 发射频率 | 最大每250ms一颗,事件可合并 |
| 点击/读屏 | pointer-events:none、aria-hidden=true |
照片本身和气泡都是向上运动,但气泡更快,不附着在照片track上;用独立overlay定位。不能用emoji字体爱心代替统一形状而在不同平台变成不同图案。可用固定SVG心形或已有图标资源。
5.2 必须真实事件驱动
- 不用随机计时器伪造他人点赞。
- iframe只订阅当前公开相册的真实likes.burst,不新增点赞写请求,也不创建虚假的own batch。
- 复用底层
RealtimeClient,新增轻量useEmbedReactions编排;不要直接调用整个useAlbumRealtime并带出观众浏览上报/点赞提交副作用。 - 不订阅slotToken当作albumToken:从公开展示位响应的album.publicToken解析。
- 事件校验:当前album、photoId、delta合法,按burstId去重;短期去重表有TTL和容量上限。
- 一个delta=100批次不创建100个节点;例如1→1颗、2–5→2颗、更大→3颗,并受全局限量;这只是视觉抽样,不修改真实统计。
- 只对当前可见照片的真实事件显示气泡,确保不会误暗示另一照片被赞。可见定义:与视口交集面积>0且高度至少20px。
- 未可见照片事件最多缓存5秒(总缓存上限50条),5秒内对应图进入可见区可展示,否则丢弃视觉事件。不为了展示旧事件让滚动跳转。
- 气泡起点取相应可见照片交集的下侧且不压操作区;上移过程裁在照片窗中。
- 收起/隐藏/切相册清空粒子和短期缓存;恢复不重放大量旧点赞。
- WebSocket失败时照片继续按取数运行,没有事件就不造气泡;不要用点赞总数差值冒充实时事件。
5.3 订阅生命周期
- 公开相册已解析且组件活跃才建立订阅。
- 一个iframe最多一个实时连接;多个相册实例不盲目共享错误token。连接池是后续优化,不要求新增宿主跨iframe中转。
- 相册换绑:先停止旧连接、清气泡和去重、清旧内容,再订阅新相册。
- 收起/隐藏超过短宽限期可断开以节省连接,恢复校准后重连;不能每一帧可见性变化都重连。
- 卸载/destroy清理socket、重连timer和事件监听。
- 暂停自动滚动不等于断开实时:hover/focus期间可接收计数事件,但reducedMotion不生成动效。
- 宿主隐藏与iframe可见性需结合父消息和document.visibilityState,不能只依赖iframe内部IntersectionObserver推断宿主遮挡。
6. 数据更新:不能抖动、复活或串相册
6.1 轮询/动画分离
- 默认公开数据轮询30000ms,复用ETag和退避;scrollSpeed是像素/秒,与轮询毫无绑定。
- 后台标签页、收起、destroy停止动画及不必要轮询,恢复立即取一次当前配置。
- 不为每张图片边界调用API。
- 每次有效响应不重置offset和头照片,不让30秒一次刷新导致回顶部。
6.2 锚定策略
更新前保存首个可见photoId及其图内偏移;更新后若仍存在,用新尺寸重算位置保持该照片相同屏幕位置。 新增图排入尚未显示区域,在下一安全边界进入;不插到用户点击中的照片前。URL更新不视作新照片。 元数据宽高变化使用同样锚定。photoId作为身份,render instance key仅用于循环实例区分。
6.3 删除、隐藏和内容窗口
- 若接口返回的是完整“近期窗口”,其缺席表示从当前轮播候选窗口淘汰,不直接声称照片被删除。
- 明确photo.hidden/deleted事件则立刻从渲染及缓存移除;正在显示的移除要选择下一有效项,清它的气泡/待点击目标。
- 区分“新增”“窗口滑出”“显式下线”,不要把队列永远追加成无限列表。
- 暂停状态也要应用隐藏/停用,不能因暂停而继续展示已撤下图。
- 相册关闭、展示位解绑/禁用返回相应不可访问状态时清照片并destroy入口。
- 普通网络错误不当作停用:保留仍有效缓存,退避重试。
- 配置版本低/旧请求响应一律丢弃;相册换绑不复用旧图片或旧事件缓存。
- 下线可见性延迟沿用现有后端通知能力并实测记录,不宣称前端能保证跨境零延迟。
7. 配置与兼容:新增v2,不破坏现有接入
7.1 推荐版本策略(执行默认)
- 新设计作为
/embed/v2/loader.js发布入口,新增输出,不覆盖旧v1语义。 - v2默认mode=vertical-stream、195×280;v1保留旧carousel,避免正在使用旧站点一夜改变尺寸。
- 可继续使用同一iframe路由并传入经过白名单的mode/version,或独立路由;必须使旧v1不渲染新footerless结构。最终选择在验收记录写清。
- 新版后台生成代码默认v2;既有站点想使用新版需更新script URL或显式选新模式,这一点在接入文档写明。
- 新构建产物采用hash或可追踪版本文件,保留稳定v2入口的合理缓存策略;本轮只产出方案/文件,不自动刷新真实CDN。
- 若仓库已有不可变hash发布实现,复用,不另造冲突机制。
7.2 新模式配置
| 配置 | 默认/规则 |
|---|---|
| displayMode | 新版vertical-stream;旧记录缺省按版本决定旧carousel |
| scrollSpeed | 16,范围8–32,单位CSS px/s |
| reactionsEnabled | true,关闭只隐藏气泡不影响照片 |
| position | right,沿用白名单 |
| offsetX/offsetBottom/zIndex | 沿用字段及范围,默认见第2节 |
| lang | auto,沿用相册语言回退 |
| collapsedDefault | 沿用用户选择优先与手机默认收起 |
| size / intervalMs / imageFit | v1仍有效;v2尺寸固定、连续原比例照片,不用旧字段改变新布局 |
优先级:版本定义的硬性尺寸 > script显式有效设置 > 展示位配置 > 默认值。v2不可通过旧data-size=md恢复320px;后台预览清楚显示其在新模式无效,而不是保存后偷偷生效。
- 缺省与显式参数继续区分。
- 新字段采用兼容扩展:更新契约、生成类型、DTO验证、国内设置、海外投影与admin编辑,缺字段有正确回退。
- 设置JSON整体替换时必须保留完整既有字段,不因新增两项丢其他配置。
- 不新增任意CSS/HTML传输。
- 数据是否需要迁移取决于当前schema字段类型;JSON新增可选字段不自动要求破坏性迁移。
7.3 消息协议
保留origin+source+type检查。
- config:只传白名单字段和configVersion;v2忽略旧size带来的尺寸变更。
- collapse/expand:用户行为;父向子同步暂停原因及实际状态,不能仅设父高度0而iframe继续动画。
- destroy:移除DOM、入口、listeners、timers和实例注册。
- resize:v1按旧行为;v2只能使用固定280或极小视口裁决后的高度,不能被子内容撑大。
- 可新增visibility/layout消息:布尔可见性、有效宽高、版本;验证数值范围,不透传对象到style。
- 多实例使用iframe contentWindow识别,不能只按相同origin广播所有状态。
- 第三方脚本伪造消息不能展开、移位或改变其他实例;不能接受任意targetOrigin='*'配置变更。
8. 宿主适配与管理预览
8.1 宿主
- iframe维持跨域隔离和最小sandbox,链接新页保持安全。
- wrapper样式限定自身,禁止修改宿主body滚动、字体、全局img/button样式。
- SPA重复插入同slot不能重复创建,移除/再引入的行为需明确;沿用已有去重能力并回归。
- 多slot同侧纵排,间隔12;空间不足后续实例收起,不重叠、不把第一实例突然放大。
- 更新窗口方向、安全区、浏览器地址栏变化时重新计算位置,但不重置滚动队列。
- 无法自动避让全部客服/Cookie浮层,提供offset/zIndex配置,不承诺智能识别所有宿主。
- 卡片边缘外不放透明大框拦截网页,阴影不影响点击范围。
8.2 后台预览
- 新增“照片上滚 / 旧版轮播”模式选择,默认新建选择新版;旧记录仍按旧模式显示。
- 新版只显示相关配置:速度(px/s)、气泡开关、位置/偏移、语言、收起默认;宽高只读195×280。
- preview用真实展示组件,数据源注入;不要再写一套静态好看的preview而生产是旧轮播。
- PC与手机预览都按同一CSS尺寸渲染,避免把preview整体缩放后误判尺寸。
- 预览可以提供宿主尺寸切换和“模拟真实点赞事件”按钮,但按钮只在管理预览工具区,不在实际组件;事件明确是测试模拟。
- 未保存预览不得修改线上slot,不上报浏览、不提交点赞、不订阅真实观众事件后当作模拟。
- 生成的接入代码使用v2,语言/参数正确;保存后真实跨源宿主页能更新。
9. 文件地图
| 位置 | 任务 |
|---|---|
| packages/embed-loader/src/params.js | v1/v2参数、速度/模式、硬性尺寸、兼容归一化 |
| packages/embed-loader/src/loader.js | 固定外壳、消息、收起/visible、多实例生命周期 |
| packages/embed-loader/package.json、sync-to-viewer.js | v2和hash产物构建/同步,保留v1 |
| packages/embed-loader/README-embed.md | 新旧接入、固定尺寸、偏移、暂停与限制 |
| apps/viewer/src/pages/EmbedPage.vue | 按版本/模式分支,接入新流与气泡 |
| apps/viewer/src/pages/embed/(现有组件) | 旧轮播保留;新建StreamViewport/StreamPhoto/ReactionLayer等 |
| apps/viewer/src/embed/useEmbedFeed.ts、feed.ts | 数据队列、版本、过期刷新、生命周期复用 |
| apps/viewer/src/embed/useVerticalPhotoStream.ts(建议新增) | rAF、锚定、有限队列、手动模式 |
| apps/viewer/src/embed/useEmbedReactions.ts(建议新增) | 轻量实时订阅、去重、可见归属与粒子限量 |
| apps/viewer/src/embed/postMessage.ts | v2消息形状与验证 |
| apps/viewer/src/realtime/ws.ts | 复用连接层,避免破坏普通观众页 |
| apps/viewer/src/i18n/locales/ | 英中文案和ARIA |
| apps/admin/src/views/system/embed-slots/ | 模式/速度/气泡/预览/代码生成 |
| apps/admin/src/types、api、utils相应模块 | 配置类型、默认值和校验 |
| packages/contracts/openapi.yaml及生成物 | 兼容字段,不手改生成文件 |
| services/api/src/embed/、embed-ov/、同步投影 | 设置验证与海外响应 |
| apps/viewer/e2e/embed.spec.ts等 | 跨源、动态、尺寸、安全回归 |
以当前文件为准追踪调用链。建议新增组件名可以调整,但职责不能散落成不可测试的大文件。共享样式不得影响普通观众页。
10. 分步执行任务
E00 基线
- 阅读目标图、当前代码及已有UI-01验收。
- 保存旧v1运行截图、尺寸、网络和配置行为。
- 记录Git已有改动、隔离测试资源,建立UI04验收记录。 出口:明确旧版保留、新版新增边界。
E01 契约和版本骨架
- v2路由/参数/构建规划,新增兼容字段。
- admin、API、海外投影、新旧缺省值一致。
- 验证旧v1配置不变,新v2固定195×280。 出口:先得到跨源可见的固定小窗,不存在footer。
E02 静态高保真样板
- 注入固定摄影数据,搭好单列照片片段、边框、收起、粒子层。
- PC1440×900、手机390×844同时截图并测量尺寸。
- 先对照修好样式,再加入滚动,不能用动画掩盖布局错误。 出口:无标题/底栏/页码/左右按钮,图片窗口形态与图一致。
E03 连续滚动
- rAF匀速、有限环形队列、预加载与失败跳过。
- 锚定更新、新照片、单图、短队列、长图验证。
- 连续录制至少30秒包含循环边界。 出口:不瞬移、不露空白、不无限增长DOM。
E04 操作和生命周期
- 点击目标锁定、拖动取消、hover/focus暂停、手动模式。
- 收起/展开/不可见/方向变化,destroy彻底清理。
- reducedMotion与键盘语义。 出口:小窗可操作,动画不会造成点错照片。
E05 实时气泡
- 只读轻量订阅当前相册、去重、可见照片归属。
- 独立粒子层、限量、真实burst驱动。
- 换绑/断线/隐藏/销毁清理,不假造事件。 出口:两个浏览器上下文中真实点赞可触发正确图的气泡。
E06 管理预览与接入
- 模式/速度/气泡设置与v2代码生成。
- 共享展示组件、未保存预览隔离、模拟事件明确标识。
- 构建同步新产物,版本/缓存说明。 出口:后台保存与第三方跨源实际效果一致。
E07 验收
- 全部第11节矩阵、静态对照、动态录屏与网络检查。
- 修复回归;普通viewer和v1不受影响。
- docs/plans/progress/UI04-验收记录.md,截图索引、待验证事项。 出口:功能/视觉/动态分别有结论,不用测试数量替代看图看视频。
任务依赖E00→E01→E02→E03→E04→E05→E06→E07;不要求额外代理。缺环境继续不受影响的工作,不把关键缺失项改成“已完成”。
11. 精确验收标准与证据
11.1 视觉测量
| 项目 | 通过标准 |
|---|---|
| 正常视口卡片 | getBoundingClientRect宽195±1、高280±1 CSS px |
| 桌面/手机尺寸 | 两端同值,不因screen width放大 |
| 边距 | 默认16/12、24+safearea,误差≤1px |
| 圆角 | 12±1px |
| 图片缝 | 3±1px |
| 收起 | 可视24、有效44,不挡其他区域点击 |
| 表面 | 无标题/底栏/页码/暂停/前后箭头/查看按钮 |
| 摄影 | 正常态真实摄影素材,无渐变色块假图 |
| 气泡 | 12/16/20、右侧小范围、无计数/头像/聊天框 |
| 外部网站 | 无样式污染、无组件外透明点击遮罩 |
截图只验证静态;不能因图上有上箭头就证明自动滚动。所有尺寸注释在比较报告中,不出现在产品UI。
11.2 动态测量
- 16px/s默认速度,在未跨循环边界的可见图锚点连续2秒向上移动约32px(±2px,测试虚拟时钟可更精确)。
- 30/60/120Hz模拟下按相同elapsed time位移一致。
- 循环边界、队列更新、URL更新不出现超过2px的无故锚点跳动;显式删图/换绑属于允许改变,另测正确性。
- 暂停后至少2秒位置不变;恢复从原位继续,不瞬间补跑隐藏时间。
- 运行5分钟后图片DOM≤设定上限,事件监听/连接不持续增加。
- 200次点赞事件压力下粒子≤8,限速符合规格,动画不提交点赞请求。
- 新照片/下线与点击同时发生不打开错误photoId。
11.3 场景矩阵
| 编号 | 场景 |
|---|---|
| EMB-A01 | PC1440×900/1920×1080,手机320×568/390×844/430×932固定尺寸 |
| EMB-A02 | 狭窄/横屏/安全区,位置和多实例不越界 |
| EMB-A03 | 横图、竖图、全景、单图、0图、2图短循环 |
| EMB-A04 | decode慢/破图/签名到期/304/接口失败,恢复正常 |
| EMB-A05 | 自动循环与新增队列锚定,DOM有上限 |
| EMB-A06 | 隐藏/删除/相册关闭/slot换绑/停用,旧数据与气泡清理 |
| EMB-A07 | 点击/拖动/悬停/键盘焦点/减少动态效果 |
| EMB-A08 | 收起与展开、刷新记忆、默认手机收起、后台设置更新 |
| EMB-A09 | 两浏览器上下文真实点赞、重复burst、错相册、不可见照片 |
| EMB-A10 | WS断线恢复,不假造气泡,照片仍能滚动 |
| EMB-A11 | 真正不同origin的宿主与iframe,消息伪造被拒 |
| EMB-A12 | 宿主全局button/img样式、客服浮层、重复script、SPA移除 |
| EMB-A13 | 后台未保存预览隔离、配置保存、v2接入代码正确 |
| EMB-A14 | v1旧接入保留,普通观众页样式/互动不回归 |
| EMB-A15 | 嵌入显示/滚动/气泡不增加相册浏览次数,不提交点赞 |
| EMB-A16 | 截图、录屏、测量、测试结果完整,不宣称未测项已通过 |
11.4 建议自动化
- 纯函数:高度计算、循环边界、锚定、配置优先级、事件归属和限量。
- 组件:零/单/多图、失败、reducedMotion、focus、销毁后不继续更新。
- Playwright:两个HTTP端口构成真实跨源,getBoundingClientRect量尺寸;截正常/空/收起/手机。
- 动态使用受控时间/rAF或测试接口复现,不靠大量真实sleep造成不稳定。
- 真实服务双上下文:一个观众页点赞,一个embed接收,核对photoId和burst去重;不能只mock事件却声称实时链路完成。
- 生命周期统计:活跃socket/rAF/timer计数、DOM数量、测试结束资源释放。
执行前核对各package脚本。已知入口:
pnpm --filter @photo/embed-loader lint
pnpm --filter @photo/embed-loader test
pnpm --filter @photo/embed-loader build
pnpm --filter @photo/embed-loader sync
pnpm --filter @photo/viewer lint
pnpm --filter @photo/viewer typecheck
pnpm --filter @photo/viewer test
pnpm --filter @photo/viewer build
pnpm --filter @photo/viewer test:e2e
新增契约/API/admin则额外运行实际对应的生成、类型、权限和E2E脚本。运行前确认测试库,禁止用生产环境初始化数据。没有运行就明确未运行,不能引用旧验收计数。
12. 交付清单和完成定义
建议目录:
docs/designs/ui04-implementation/
reference/ 原图/裁片索引,不伪装实际截图
actual/desktop/
actual/mobile/
comparisons/
recordings/ 含循环边界/点赞/暂停恢复的录屏
measurements/ CSS尺寸、位移、DOM/连接统计
README.md 复现入口、样本数据和截图说明
docs/plans/progress/UI04-验收记录.md
验收记录包含:
- 基线commit与现有工作树改动保护。
- E00–E07完成表,修改文件/接口/版本兼容策略。
- EMB-A01–A16逐项通过/失败/待验及证据链接。
- 195×280实测结果、运动位移、资源上限。
- v1/v2接入样例、未保存预览与真实服务验证说明。
- 未完成项、外部资源、真实CDN部署不在本轮的说明。
完成定义:静态外观、连续运动、真实互动和安全兼容四者都具备证据。 只有静态截图、只有单测通过、只有示例气泡或仍保留旧底栏,均不能标记完成。
13. 可直接复制给 Claude 的执行指令
请执行 docs/plans/2026-09-22-UI04-嵌入组件上滚照片窗高保真实施方案.md,先打开 docs/designs/embed-widget-scroll-hearts-2026-09-22.png。目标是PC/手机固定195×280 CSS px、单列照片连续向上滚动、真实点赞心形气泡、无标题/底部工具栏,仅右上收起,点击照片进入对应观众深链。不是旧轮播改颜色。按E00–E07先做固定尺寸静态样板并量尺寸,再实现有限队列滚动、暂停/点击、真实事件、管理预览和v2接入,保留旧v1兼容。不得假造点赞、不得用50vw放大PC、不得用随机动画代替实时数据。保护已有改动,不影响App/普通viewer,不自动发布。最终交付真实实现、跨源截图、动态录屏、尺寸/位移测量、测试结果及UI04验收记录;不完整项如实标待验,不只返回方案。
