Files
photo-live/docs/plans/2026-09-22-UI04-嵌入组件上滚照片窗高保真实施方案.md
T

32 KiB
Raw Blame History

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。

  1. 计算照片高度及3px间距,装入足够覆盖窗口和预加载区域的条目。
  2. 保存头实例ID、头部被移出像素offset及sequence游标。
  3. rAF增加offset,track translateY(-offset)。
  4. offset超过头条目高度+gap时,将头条目退役,offset减去该值,并在尾部补下一张;跨界过程应保持屏幕内容相同。
  5. render实例key包含循环代次,避免photoId重复出现造成重复key。
  6. 只保留可见窗口+前后缓冲。建议最多16个DOM图片实例、数据队列最多30张;尺寸极矮照片无法填满窗口时使用受控补位或静态回退,不能无限扩DOM。
  7. 内容两张以上但一轮高度短于窗口,可使用少量可见循环副本填充;副本不要全部可聚焦。相同photoId的非主要副本对辅助技术隐藏,并处理点击到对应原图。
  8. 仅一张照片时静态展示,不把同一张复制成刷屏。单张长图在减少动画/手动模式下可滚动查看;自动状态优先完整contain单张,不上下无穷重复。
  9. 无照片使用空态,不启动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

验收记录包含:

  1. 基线commit与现有工作树改动保护。
  2. E00–E07完成表,修改文件/接口/版本兼容策略。
  3. EMB-A01–A16逐项通过/失败/待验及证据链接。
  4. 195×280实测结果、运动位移、资源上限。
  5. v1/v2接入样例、未保存预览与真实服务验证说明。
  6. 未完成项、外部资源、真实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验收记录;不完整项如实标待验,不只返回方案。