如果你做过 UniApp 图片选择预览功能,大概率遇到过这种情况:选完图直接扔给 uni.previewImage,结果样式、手势、按钮统统没法改,用户想加个下载或查看原图,门儿都没有。想实现一套完全可控的预览效果,关键不在换选图 API,而是从选图那一刻就接管后续的展示链路——选图后把 tempFilePaths 存到 data,用自定义组件渲染缩略图,点击时触发自定义预览层。缩放、拖拽、滑动这三类手势得自己手写,还得处理好 H5 和小程序的兼容差异,以及图片加载失败时的兜底逻辑。

uni.chooseImage 选图后怎么接自定义预览逻辑
很多人走弯路,选完图就立马把 tempFilePaths 塞给 uni.previewImage,结果界面完全不受控,连加个自定义按钮都做不到。正确的做法是:选图成功后,先把 res.tempFilePaths 存进 imageList 数组,不急着预览。页面上用 渲染缩略图,绑定 @click="openPreview(index)" 触发自定义预览。而 openPreview 方法只做两件事:设置当前索引、把 previewVisible 置为 true。真正的预览组件通过 props 接收 urls 和 current,完全脱离 uni API 控制渲染。
自定义预览组件必须处理的三个手势场景
用户点开图片,预期是三件事:缩放查看细节、拖拽移动位置、左右滑动切换图片。这仨缺一不可,但 和 组合起来很容易出问题,尤其 H5 和小程序表现不一致。最常见的坑是:只实现了双指缩放,但没处理单指拖拽时缩放态下的位移;或者 swiper 切图时 movable-view 没重置 transform,导致下一张图继承上一张的偏移量,位置直接乱掉。
- 缩放靠
touchstart+touchmove判断多指距离变化,更新scale值,同时禁用 swiper 的 touchmove 防止冲突 - 拖拽只在
scale > 1时才启用,每次touchend后记录offsetX/offsetY,不能依赖 CSS transition 过渡 - 切换 swiper 项时,必须手动重置
scale=1、offsetX=0、offsetY=0,否则手势状态会残留
为什么不能直接用 uni-ui 的 uni-gallery
uni-gallery 确实封装了基础预览,但它把图片加载、缩放、指示器全揉在一起,不暴露内部 transform 控制权。想加个下载按钮、水印、长按保存?改不了。更实际的痛点:它默认用 mode="widthFix" 渲染图片,遇到超长截图或文档扫描件会严重变形;自己写的组件可以按需判断宽高比,动态设 mode 或加 scroll-view 包裹。
- 如果只需要数字指示器加左右滑,
uni-gallery够用;但凡要加「保存到相册」「复制链接」「查看原图大小」,就得自己动手 - 它的缩放基于
transform: scale(),在某些 Android 微信 WebView 下会出现重绘闪烁;自定义组件可以用zoom: scale()+overflow: hidden规避这个问题 - H5 端它依赖
position: fixed遮罩,滚动页面时遮罩可能错位;自定义组件改用position: absolute+z-index精确控制层级,更稳
预览组件里图片加载失败怎么兜底
用户网络差、图片链接过期或 CDN 返回 404,预览页容易卡在 loading 或者直接显示空白。官方 uni.previewImage 会自动 fallback 到占位图,但自定义组件不会。别只靠 @error 事件——它在某些平台(比如支付宝小程序)压根不触发,而且无法区分是加载失败还是图片本身为空。
- 给每张图维护一个
loadStatus数组,初始为'loading',@load改为'success',@error改为'failed' - 在
swiper-item内条件渲染:v-if="loadStatus[index] === 'success'"显示图片,else-if显示灰色占位 + 重试按钮 - 重试逻辑要防抖,避免用户狂点多次请求;失败时 fallback 到 base64 占位符,而不是留白
总结一下:自定义风格的核心不在于怎么画 UI,而在于怎么接管交互生命周期——从选图那一刻起,所有状态都得自己管,包括缩放锚点、滑动边界、加载队列、失败重试。任何一个环节漏掉,用户都会觉得“卡”“错位”“点不动”。
