一、问题与目标
纹渊的纹样不是简单贴一张平面图。用户选择纹样后,需要在杯体这种带弧度的载体上看到预览效果;如果直接按矩形绘制,图案会显得生硬,边缘也容易和杯身透视不一致。
Canvas 在这里承担的是“轻量预览层”的角色。它不负责最终 3D 渲染,却要在用户选择纹样、切换载体、调整模式时快速给出反馈。曲面映射、重绘时机和状态同步如果没有处理好,用户会看到旧图残留、选中态错乱或预览闪动。

二、实现思路
实现时可以把绘制拆成三步:先把纹样资源归一化成可绘制对象,再按杯体区域切成多条竖向 band,最后根据 band 的位置计算宽度压缩和透明边缘。这样虽然不是完整 3D 投影,但足够表达杯身弧度。
重绘触发点要少而明确。纹样变更、载体变更、画布尺寸变化和模式切换会触发绘制;普通页面刷新不应该重复创建资源。否则在平板或 2in1 上频繁调整窗口时,预览层容易出现抖动。
| 绘制环节 | 处理方式 |
|---|---|
| 资源准备 | 把纹样图片、默认底图和空状态统一成可绘制对象 |
| 曲面切片 | 按横向位置拆分 band,用宽度和透明度模拟杯体弧度 |
| 重绘控制 | 只在纹样、载体或尺寸变化时触发 Canvas 刷新 |
| 结果输出 | 预览图可以继续交给导出、分享或 3D 材质链路 |
三、关键实现
曲面效果的关键在 band 的宽度和位置计算。下面这个片段把杯身分成多个窄条,每个窄条按自身所在位置做压缩,最终形成类似包裹在杯面上的视觉效果。
private drawCurvedBands(ctx: CanvasRenderingContext2D): void {
const bandCount = 21
const bandWidth = this.canvasWidth / bandCount
for (let i = 0; i < bandCount; i++) {
const u = this.cupWarpU(i)
const scaleX = 0.72 + Math.sin(u * Math.PI) * 0.28
const alpha = 0.42 + Math.sin(u * Math.PI) * 0.5
ctx.save()
ctx.globalAlpha = alpha
ctx.translate(i * bandWidth + bandWidth / 2, 0)
ctx.scale(scaleX, 1)
ctx.drawImage(this.patternImage, -bandWidth / 2, 0, bandWidth, this.canvasHeight)
ctx.restore()
}
}
这段逻辑的好处是计算成本低,不需要加载 3D 场景也能给出接近载体形状的反馈。对于创作页来说,用户最先需要判断的是纹样方向、密度和色块关系是否合适,Canvas 预览足够承担这个即时反馈。
需要注意的是,Canvas 绘制结果必须和页面状态绑定,而不是和某一次点击绑定。用户连续切换纹样时,旧绘制任务应该被新状态覆盖;否则看似只是慢半拍,实际会让后续导出或 3D 贴图拿到错误纹样。
四、失败场景与取舍
Canvas 预览最容易出现三类问题:资源尚未加载就开始绘制,导致空白;窗口尺寸变化后没有重新计算 band,导致变形;用户快速切换纹样时旧结果覆盖新结果,导致选中态和预览不一致。
取舍上,创作页不需要在 Canvas 层追求真实物理投影,而要优先保证反馈速度和状态一致。精细材质效果交给 3D 预览,Canvas 负责低成本确认图案方向和整体观感,两层职责分开后,问题会更容易定位。
五、验证步骤
- 打开创作入口,确认标题、主要卡片、底部操作区和预览区域显示正常。
- 走一遍主流程,观察加载态、成功态和下一步按钮是否同步变化。
- 人为制造空输入、重复点击或不可用条件,确认页面给出可理解的提示。
- 返回上一页再进入,确认选中态、预览结果和关键状态没有漂移。
- 在较窄窗口和较宽窗口各看一遍,确认文字、卡片和按钮没有重叠。
六、总结
Canvas 曲面预览的重点是快速、稳定、可恢复。只要资源准备、曲面切片和重绘触发点清楚,创作页就能在不依赖完整 3D 场景的情况下给用户可靠反馈。
:Canvas 曲面纹样渲染与重绘控制&spm=1001.2101.3001.5002&articleId=162987993&d=1&t=3&u=a6b4d741439c4535abe5454899ce2fb4)
744

被折叠的 条评论
为什么被折叠?



