2.3 商品详情:图片预览与富文本
场景
详情页是顾客做决定的地方,也是设计稿和实际效果差距最大的地方。第一版详情页照着设计稿做:头部轮播五张大图,中间一段富文本图文,底部固定购买栏。真机上一打开,富文本里的图直接溢出屏幕——设计师做的是 750px 宽的图,某些图的原始宽度超过了小程序容器,左右各冒出一段;点击放大没有做,顾客想看杯子细节只能把眼睛凑近屏幕。
还有几个隐形问题:规格选择。第一版详情页里规格是个朴素的下拉,库存、限购这些信息全没有,顾客选完规格点了购买,才在下单时被提示“该规格已售罄”,浓浓的旧系统味道——这种感觉正是我们要消除的。总之第一版详情页的问题清单列下来有七条,全是“设计稿很美、真机见鬼”的典型。逐条修就是这后半节的内容。
朋友看了一眼说:在我店里,顾客是把杯子拿起来翻来覆去看的,还凑近看釉面。你总得让人能放大看吧。后来她还提了个很具体的要求:详情里要能看到这个杯子是哪位匠人做的、哪一窑出的,这些是她店里讲故事的方式,光放图不够。这节讲的就是详情页里两件最影响转化的事:图片预览和富文本渲染,顺带把规格选择的交互一并说了。三者合起来,就是详情页的“看得清、读得顺、买得对”。
方案
详情页结构分成四层,各管各的:顶部轮播(首图优先加载,其余懒加载)、富文本详情(统一宽度适配、图片可点放大)、规格选择区(SKU 列表带库存提示)、底部固定购买栏(加车与立即购买)。我把四层的核心问题列了一张表:
| 层 | 体验问题 | 方案 |
|---|---|---|
| 轮播 | 首图加载慢 | 首图预加载加 lazy-load |
| 富文本 | 图片溢出、宽度不一 | 渲染前统一改样式 |
| 图片预览 | 不能放大 | wx.previewImage |
| 底部购买栏 | 滑走后消失 | 固定定位加安全区 |
| 规格选择 | 选错规格买错货 | 高亮选中态加库存提示 |
富文本渲染我对比过三种路子:原生 rich-text 组件(简单但交互弱)、web-view 内嵌网页(能力强但体验割裂、要备案域名)、服务端转码成节点再渲染(可控但要造轮子)。最终选 rich-text 加服务端预处理:富文本内容在云函数或上传时就转成带统一样式的节点,前端直接渲染,又快又稳,备案域名的麻烦也一并省了。
规格选择区的交互值得多说一句:顾客最怕的是选错规格。我们做的方案是选中态高亮放大、未选规格的库存实时显示在标签后面(如“白釉 / 300ml · 余 12 件”),库存为 0 的规格直接置灰不可点。这样顾客在点选之前就心里有数,减少下单后被拦下的情况——被拦下的订单几乎不会回来,这是转化率里看不见但很贵的那一部分。
这套设计的灵感来自我们自己的观察:店里导购给顾客推荐时,手里总习惯性拿起两只杯子对比给顾客看。线上没有这个实体动作,就要用更明确的信息替代它。
配置/代码
详情页的数据由云函数 getDetail 聚合返回:一条 SPU 加它名下全部在售 SKU。拿数据之前先说一句字段卫生:详情返回里不要带 detailHtml 之外的多余大字段,列表页返回的字段更精简,接口分工不同各取所需。拿到数据后前端绑定的核心是图片预览,小程序自带一行调用:
// 点击富文本或轮播里的图,调起全屏预览
onImageTap(e) {
const current = e.currentTarget.dataset.src;
wx.previewImage({
current,
urls: this.data.previewUrls
});
}
previewImage 的 urls 必须是图片网络地址数组,所以我们把云存储的 fileID 统一换成 HTTPS 的 CDN 地址再塞进去——fileID 在 previewImage 里是不认的,这是第一个坑。富文本的预处理在数据回来之后做一遍,核心是把所有 img 的宽度锁定百分之百、高度自适应:
// 富文本 html 里的 img 统一加最大宽度样式
function fixRichImages(html) {
return html.replace(/<img/gi,
'<img style="max-width:100%;height:auto;display:block;"');
}
这一行正则把所有图片的宽度驯服了,无论设计师上传的图多宽都不会溢出。这也是内容治理的一部分:我们不允许运营手动调样式,所有富文本进库前过一遍这道预处理,保证整站视觉整齐,也让每个详情页看起来都像同一个店出的。轮播部分用 swiper 组件,首图绑 CDN 压缩版本,其余项加 lazy-load,顾客滑到再加载。
底部购买栏固定在视口底部,加 padding-bottom: env(safe-area-inset-bottom) 给全面板的安全区留位,否则按钮会被小白条遮住一截,点起来像点在玻璃上,这是我亲眼看着一位顾客点了三次没点动发现的。
规格选择与价格联动是详情页里唯一带状态的部分,逻辑不复杂但要理顺:选中的 SKU 变化时,购买按钮上的价格、库存提示和限购文案一起更新;没选规格时按钮显示“请选择规格”,选完才变成“加入购物车”。核心代码很短:
// 选择规格后联动价格与按钮状态
onSpecTap(e) {
const skuId = e.currentTarget.dataset.id;
const sku = this.data.skuList.find(s => s._id === skuId);
this.setData({
selectedSku: sku,
priceText: (sku.price / 100).toFixed(2),
stockTip: sku.stock > 0 ? `仅剩 ${sku.stock} 件` : "已售罄",
canBuy: sku.stock > 0
});
}
限购的提示也挂在这里:limitPerUser 大于 0 时,在库存提示后面追加“每人限购 N 件”。细节虽然零碎,但每一条都能直接减少客服答疑的压力,把客服回复从日常变成例外,导购也能腾出手来泡茶。
踩坑
第一个坑就是 fileID 不能预览。富文本里存的图全是 cloud:// 协议的 fileID,previewImage 直接传一片黑,第一次调试时我盯着黑屏还以为是网络问题。解决办法是在云函数里用 cloud.getTempFileIDs 批量换成可访问链接。临时链接有有效期的问题怎么办?详情页数据本身缓存时间短,链接过期前数据早刷新了,不冲突。
第二个坑是富文本里混了视频。设计师传了几段烧窑的短视频,rich-text 原生不支持 video 标签,直接消失,顾客完全看不出那里曾经有段视频。方案是富文本只放图,视频单独抽出来用 video 组件渲染在详情页上方,属于给精品店加分的特殊模块,朋友后来给每件主打商品都配了一段三十秒短视频。
第三个坑是轮播图首图太大。一张烧瓷窑的高清原图三兆多,首屏又回到等图的老路上,在 4G 下要转好几圈。和设计约定:首图统一压到宽度 750、质量八十五的压缩版上传,原图只在顾客预览时拉取。详情首屏因此又快了小一秒,这个压缩规范后来也写进了运营手册。
第四个坑是富文本里的图片链接过期。早期富文本存的是第三方图床链接,图床政策调整后全部失效,详情页一章白屏,那批商品整整一天没能看。痛定思痛:所有内容图一律搬到自己的云存储,链接换成 fileID,命运握在自己手里。
第五个坑是底部购买栏挡住内容。固定栏会遮住富文本最后一段,矮个子机型尤其明显,有顾客反馈“故事看一半没了”。在页面最外层容器加了与购买栏等高的占位高度,内容尾巴终于能完整露出来。
第六个坑是预览图顺序反了。顾客从详情第三张图点开预览,current 传了当前图但 urls 没按顺序排,左右滑的时候乱跳,顾客还以为图串了。预览数组按页面顺序排好后再传即可,细节但致命,低级错误最伤口碑。
第七个坑是富文本编辑入口缺失。一开始只有我能改富文本,朋友想加一句“到货通知”都得找我,沟通成本极高。后来给她做了个简单的后台富文本编辑页,所见即所得,她从此把详情页当朋友圈发,上新速度反而比我们开发设想的还快,详情页的内容厚度也成了这家店区别于连锁品牌的一种气质。
小结
详情页的活儿细而碎,但归纳起来就两条:图片要能让人看清楚(预览、压缩、尺寸可控),内容要能在任何机型上不穿帮(富文本兜底、安全区、占位)。这些细节加起来效果很实在——改完一周,详情页的平均停留时长从一分钟出头涨到两分半,加车率也跟着走了,朋友那天翻后台数据翻得合不拢嘴,还说了一句到位的话:做不到让顾客把杯子拿在手里,至少让他在屏幕上翻来覆去看清楚。规格选择那块尤其值得投入,因为它直接降低了下单链路的流失。如果商品详情里有视频或者直播回放的需求,可以参考实时音视频课堂实战里对音视频模块的集成思路。下一节解决图床这个根子问题:云存储加 CDN,让两千张图都不拖后腿。
延伸阅读
- 图床方案:云存储与 CDN 加速:下一节,图片又快又稳
- 小程序信息流实战:图片列表的性能处理
- 回到本书目录:全书五篇结构一览
