:Canvas 曲面纹样渲染与重绘控制)
一、问题与目标纹渊的纹样不是简单贴一张平面图。用户选择纹样后需要在杯体这种带弧度的载体上看到预览效果如果直接按矩形绘制图案会显得生硬边缘也容易和杯身透视不一致。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 场景的情况下给用户可靠反馈。