5.3 KiB
5.3 KiB
图片/视频预览自适应经验总结
更新日期:2026-04-30
背景
统一媒体预览器在展示横图、竖图、4K 大图和视频时,曾出现三个典型问题:
- 横图被按竖图思路缩放,展示区域浪费明显。
- 放大后的媒体偏下,和底部工具条重叠。
- 切换 4K 图片时,首帧先以 100% 或旧缩放比例显示,再闪缩到自适应尺寸。
这类问题不能只调 CSS 的 object-fit。预览器同时存在 CSS contain、React 状态缩放、拖拽偏移、视频 metadata、图片自然尺寸、工具条遮挡等因素,必须统一处理首帧、尺寸计算和交互状态。
经验原则
-
自适应目标是“完整显示并尽量利用可见区域”。
- 横图应优先吃满可用宽度。
- 竖图应优先吃满可用高度。
- 不应为填满高度而默认裁切横图,除非产品明确进入 cover/裁切模式。
-
避免 CSS contain 后再按原图像素二次缩小。
<img>/<video>先由 CSSmax-width/max-height得到基础渲染尺寸。- JS 再基于这个基础尺寸计算还能放大或缩小多少。
- 不要直接用
naturalWidth/naturalHeight对视口做第二次 contain,否则横图容易被过度缩小。
-
首帧必须先 fit,再显示。
- 图片需要等
complete + naturalWidth或onLoad后再计算。 - 视频需要等
loadedmetadata后读取videoWidth/videoHeight。 - fit 未完成时用
visibility: hidden隐藏内容,仍保留布局和测量能力。 - 不要用
display: none,否则offsetWidth/offsetHeight会失效。
- 图片需要等
-
自动 fit 应尽量在绘制前完成。
- 切换媒体、同步外部 zoom/pan、图片已加载检查适合使用
useLayoutEffect。 - 避免在首帧 fit 上使用
requestAnimationFrame,否则会先绘制中间尺寸。 - 普通 ResizeObserver 后续修正可以保留,但要避免重复设置同样的 pan/zoom。
- 切换媒体、同步外部 zoom/pan、图片已加载检查适合使用
-
工具条遮挡应进入可用区域计算。
- 底部工具条占用的高度应作为
verticalReserve。 - 自动位置可上移
verticalReserve / 2,让媒体中心对齐到真正可见区域中心。 - 用户手动拖拽或缩放后,不再强行自动覆盖位置。
- 底部工具条占用的高度应作为
-
自动适配不要制造动画。
- 预览内容的
transform不应有 transition。 - 切换媒体时应直接显示最终 transform。
- 用户滚轮缩放也应使用稳定步进和 clamp,避免高频滚轮造成跳变过大。
- 预览内容的
-
性能与内存约束。
- 不读取图片二进制,不把 4K 图转成 base64 存状态。
- 只读取 DOM 尺寸和媒体自然宽高。
- 状态更新前比较当前值,避免 ResizeObserver 下重复渲染。
代码层面固化的规则
1. 自动 fit 基于基础渲染尺寸
优先读取媒体元素的 offsetWidth/offsetHeight。当元素还没有 offset 时,再用 getBoundingClientRect() / currentZoom 兜底。
计算公式:
availableWidth = viewportWidth - horizontalPadding
availableHeight = viewportHeight - verticalReserve
fittedZoom = min(
availableWidth / baseRenderedWidth,
availableHeight / baseRenderedHeight
)
2. 首帧隐藏直到 fit 完成
单图图片/视频需要自动 fit 时:
- 切换媒体后设置
isAutoFitReady = false。 - 图片
onLoad或视频onLoadedMetadata后计算 fit。 - fit 成功后设置
isAutoFitReady = true。 - 加载失败或用户手动介入时解除隐藏,避免卡住。
3. 工具条避让用自动 pan
自动 fit 完成时设置:
pan = { x: 0, y: -verticalReserve / 2 }
这会把媒体整体上移到可见区域中心,避免压到底部工具条。
4. 用户操作优先
手动拖拽、滚轮、工具栏缩放后标记 hasManualViewChange。后续 ResizeObserver 或加载回调不再覆盖用户视图。
检查清单
- 横图:宽度尽量铺满,不出现大量上下浪费。
- 竖图:高度尽量铺满,完整显示。
- 4K 大图:切换时不出现 100% 到自适应尺寸的闪缩。
- 视频:metadata 到位后直接按正确尺寸展示。
- 底部工具条:不遮挡主要画面。
- 手动缩放/拖拽:不被自动 fit 抢回。
- 对比模式:不受单图自动 fit 逻辑干扰。
- 性能:不引入大文件读取、base64 存储或频繁无效状态更新。
验证建议
pnpm --dir packages/drawnix exec eslint src/components/shared/media-preview/MediaViewport.tsx --ext .tsx --rule '@nx/enforce-module-boundaries: off'
git diff --check -- packages/drawnix/src/components/shared/media-preview/MediaViewport.tsx packages/drawnix/src/components/shared/media-preview/MediaViewport.scss docs/MEDIA_PREVIEW_FIT_LESSONS.md
若仓库全量类型检查恢复干净后,再补跑:
pnpm --dir packages/drawnix exec tsc -p tsconfig.lib.json --noEmit --pretty false
提交备注模板
问题描述:
- 图片/视频预览自适应不区分横竖图,4K 图切换时会先显示中间尺寸再闪缩。
修复思路:
- 基于媒体基础渲染尺寸计算 contain zoom。
- 首帧自动 fit 完成前隐藏媒体内容,避免 100% 闪缩。
- 自动 pan 上移避让底部工具条,并移除 transform 过渡。
更新代码架构:
- MediaViewport 收口自动 fit、手动视图保护和 fit-ready 展示门槛。
- MediaViewport 样式增加 auto-fitting 隐藏态。