feat: 用 Canvas 替换 CSS overlay 实现标题预览 #1246

Merged
xiaoxia merged 2 commits from feat/title-preview-canvas into develop 2026-08-05 19:04:18 +08:00
Owner

改动说明

根因: 当前用 <div> + CSS textAlign/wordBreak/flex 叠加标题,CSS 文字布局和 ASS 字幕渲染的文字引擎不同,不同字数/字号/字体下行为不可预测,已反复修复仍不生效。

方案: 在视频画面上用 <canvas> 绘制标题文字,放弃 CSS overlay。

主要改动

  • 新增 wrapText() 逐字换行函数(与 ASS 换行行为一致)
  • 新增 drawTitleOnCanvas() 统一绘制函数(支持描边/阴影/多行居中/高清屏适配)
  • video 模式:canvas 绝对定位叠加在视频容器上(paddingX=40, topOffset=60)
  • no-video 模式:canvas 叠加在暗色预览区(paddingX=20, topOffset=30, 字号缩放0.75)
  • ResizeObserver 监听容器尺寸变化,窗口缩放时自动重绘
  • video loadedmetadata 事件触发重绘
  • 删除 positionOverlayStyle 函数和 buildTitleStyle CSS 函数
  • error 状态下 canvas 不渲染(与之前逻辑一致)

验收标准

  • 短标题(如"你好")居中显示
  • 长标题(如"致每一个在短视频路上坚持的人")自动换行且居中
  • 不同字号(24px~36px)都正确渲染
  • 不同位置(top/center/bottom)都正确
  • 窗口缩放时标题自适应
  • 预览成功和 idle/loading/error 状态都正常
## 改动说明 **根因:** 当前用 `<div>` + CSS textAlign/wordBreak/flex 叠加标题,CSS 文字布局和 ASS 字幕渲染的文字引擎不同,不同字数/字号/字体下行为不可预测,已反复修复仍不生效。 **方案:** 在视频画面上用 `<canvas>` 绘制标题文字,放弃 CSS overlay。 ### 主要改动 - 新增 `wrapText()` 逐字换行函数(与 ASS 换行行为一致) - 新增 `drawTitleOnCanvas()` 统一绘制函数(支持描边/阴影/多行居中/高清屏适配) - video 模式:canvas 绝对定位叠加在视频容器上(paddingX=40, topOffset=60) - no-video 模式:canvas 叠加在暗色预览区(paddingX=20, topOffset=30, 字号缩放0.75) - ResizeObserver 监听容器尺寸变化,窗口缩放时自动重绘 - video `loadedmetadata` 事件触发重绘 - 删除 `positionOverlayStyle` 函数和 `buildTitleStyle` CSS 函数 - error 状态下 canvas 不渲染(与之前逻辑一致) ### 验收标准 - [ ] 短标题(如"你好")居中显示 - [ ] 长标题(如"致每一个在短视频路上坚持的人")自动换行且居中 - [ ] 不同字号(24px~36px)都正确渲染 - [ ] 不同位置(top/center/bottom)都正确 - [ ] 窗口缩放时标题自适应 - [ ] 预览成功和 idle/loading/error 状态都正常
xiaoxia added 1 commit 2026-08-05 18:54:14 +08:00
feat: 用 Canvas 替换 CSS overlay 实现标题预览
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 27s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 50s
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 50s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 44s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m6s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m36s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m36s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m38s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m54s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
5f427386e3
根因:CSS 文字布局与 ASS 字幕渲染引擎不同,不同字数/字号/字体下行为不可预测。
方案:用 canvas 绘制标题,放弃 CSS overlay。

- 新增 wrapText() 逐字换行函数
- 新增 drawTitleOnCanvas() 统一绘制函数(描边/阴影/多行居中/高清屏)
- video 模式 canvas 叠加在视频容器上
- no-video 模式 canvas 叠加在暗色预览区(字号缩放0.75)
- ResizeObserver 监听容器变化,窗口缩放自动重绘
- 删除 positionOverlayStyle 和 buildTitleStyle CSS 函数
- error 状态 canvas 不渲染

Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>

🚀 预览环境已部署

项目 详情
PR号 #1246
预览链接 https://pr-1246.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1246 | | 预览链接 | [https://pr-1246.preview.xiaoxiajianji.com](https://pr-1246.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
xiaoxia added 1 commit 2026-08-05 18:57:57 +08:00
style: prettier 格式化 Canvas 标题预览文件
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 44s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 44s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m2s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 43s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m33s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m35s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m39s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m1s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m37s
AI Code Review / AI Code Review (pull_request) Successful in 2m28s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 3m40s
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 42s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m7s
43d4cd041c
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
auto-approve-bot approved these changes 2026-08-05 18:59:33 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
auto-approve-bot approved these changes 2026-08-05 18:59:33 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
Collaborator

【阻塞级判定】

  • 是否存在阻塞级问题:否
  • 阻塞级问题数量:0 个

📊 审查概览

  • 整体评价:通过
  • 建议级问题数量:2 个

🔴 阻塞级问题(必须修复)

💡 改进建议(不阻塞合并)

  1. [PreviewVideoPanel.tsx: 180-186] ResizeObserver 回调性能优化

    • 具体内容:在 ResizeObserver 的回调中直接调用 drawVideoTitle/drawNoVideoTitle 可能会导致在窗口拖拽缩放时频繁触发重绘,造成性能开销。建议使用 requestAnimationFrame 对回调进行节流,确保每一帧只绘制一次,提升交互流畅度。
  2. [PreviewVideoPanel.tsx: 55] 缺少对 TitleSettings 属性的防御性检查

    • 具体内容:drawTitleOnCanvas 函数中直接使用 settings.sizesettings.font。如果传入的 titleSettings 对象属性不完整(例如为空对象 {}),会导致 fontSize 计算为 NaN,进而导致 ctx.font 设置无效,文字无法绘制。建议增加默认值处理(如 settings.size || 16)或确保上游数据完整性。

良好实践

  • 使用 pointerEvents: "none" 确保 Canvas 覆盖层不会阻挡视频控件的交互操作。
  • 使用 ResizeObserver 替代 window.onresize,能够更精准地监听容器尺寸变化。
  • Canvas 绘制逻辑中考虑了 devicePixelRatio,确保在高分屏下文字清晰不模糊。
  • useEffect 清理函数中正确调用了 observer.disconnect(),避免了内存泄漏风险。
  • 文本换行逻辑 (wrapText) 使用逐字测量,能较好地模拟 ASS 字幕的渲染行为。

🤖 由 AI 代码审查机器人自动生成 | 2026-08-05 11:00:27 | 模型:

### 【阻塞级判定】 - 是否存在阻塞级问题:否 - 阻塞级问题数量:0 个 ### 📊 审查概览 - 整体评价:通过 - 建议级问题数量:2 个 ### 🔴 阻塞级问题(必须修复) 无 ### 💡 改进建议(不阻塞合并) 1. **[PreviewVideoPanel.tsx: 180-186] ResizeObserver 回调性能优化** - 具体内容:在 `ResizeObserver` 的回调中直接调用 `drawVideoTitle`/`drawNoVideoTitle` 可能会导致在窗口拖拽缩放时频繁触发重绘,造成性能开销。建议使用 `requestAnimationFrame` 对回调进行节流,确保每一帧只绘制一次,提升交互流畅度。 2. **[PreviewVideoPanel.tsx: 55] 缺少对 TitleSettings 属性的防御性检查** - 具体内容:`drawTitleOnCanvas` 函数中直接使用 `settings.size` 和 `settings.font`。如果传入的 `titleSettings` 对象属性不完整(例如为空对象 `{}`),会导致 `fontSize` 计算为 `NaN`,进而导致 `ctx.font` 设置无效,文字无法绘制。建议增加默认值处理(如 `settings.size || 16`)或确保上游数据完整性。 ### ✅ 良好实践 - 使用 `pointerEvents: "none"` 确保 Canvas 覆盖层不会阻挡视频控件的交互操作。 - 使用 `ResizeObserver` 替代 `window.onresize`,能够更精准地监听容器尺寸变化。 - Canvas 绘制逻辑中考虑了 `devicePixelRatio`,确保在高分屏下文字清晰不模糊。 - `useEffect` 清理函数中正确调用了 `observer.disconnect()`,避免了内存泄漏风险。 - 文本换行逻辑 (`wrapText`) 使用逐字测量,能较好地模拟 ASS 字幕的渲染行为。 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-08-05 11:00:27 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
xiaoxia merged commit bc7d00b470 into develop 2026-08-05 19:04:18 +08:00

🗑️ 预览环境已清理

PR #1246 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1246 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.