相册在日常生活中经常使用到,如手机自带相册、朋友圈、商品展示图、评论贴图等等,都经常用到相册的能力。
👇下面演示 iOS 原生相册、朋友圈等相册使用效果,我们可以看到图片切换非常顺滑,视觉焦点不变。
😭 但是在小程序中,页面切换会有明显的切换感。用户焦点会丢失,缺少视觉关联性。
共享元素
🔥 为了丰富用户交互效果、提升用户体验、增强视觉关联性,小程序支持了页面间的共享元素
下图展示有无共享元素的页面切换效果,可以看出使用共享元素之后,转场动画更灵活
共享元素 经常作用在图片上,例如上面示例中的相册效果,是那么共享元素动画要怎么实现呢?
在页面跳转时,两个页面 key 相同的 share-element 组件则会产生飞跃的过渡效果
在上一篇文章中,我们学习了 页面转场动画,共享元素动画跟页面转场动画是类似的,同样是在页面切换间的动画。
动画进度、时间 与 路由进度、时间保持一致(非自定义路由也支持共享元素动画)
在共享元素飞跃的过程中,前后页面图片的裁剪方式(mode) 可能不一致
这种情况下容易导致图片突然跳变,所以我们需要在飞跃的过程中改变图片的大小来保证平滑飞跃
在共享元素动画进行的过程中,share-element 可以收到 onFrame 表示动画帧回调
我们可以在帧回调中处理内部元素的显示
例如:我们这里通过在帧回调中改变图片宽高来达到平滑飞跃的效果
// .wxml
<share-element key="binnie" onFrame="onFrame">
<image bind:load="onImageLoad" />
</share-element>
// .js
// 初始化
attached() {
this.aspectRatio = shared(0)
this.curRect = shared(undefined)
// 绑定 worklet 动画
this.applyAnimatedStyle('.img', () => {
'worklet'
const curRect = this.curRect.value
return {
left: `${curRect.left}px`,
top: `${curRect.top}px`,
width: `${curRect.width}px`,
height: `${curRect.height}px`
}
})
},
// 获取图片初始宽高比
onImageLoad(e) {
const { width, height } = e.detail
this.aspectRatio.value = width / height
},
// 动画帧回调,调整图片大小
onFrame(data) {
'worklet'
// 当前帧容器的宽高、进度等信息
const { begin, end, progress, direction } = data
...
// 根据图片初始宽高比、共享元素容器、动画进度等计算出变化过程中的值
this.curRect.value = {
left = lerp(begin.left, end.left, t),
top = lerp(begin.top, end.top, t),
width = lerp(begin.width, end.width, t),
height = lerp(begin.height, end.height, t),
}
}
更多共享元素动画原理请查看 官方文档
手势搭配
打开图片之后,我们经常需要用到手势来操作图片,如缩放、移动、双击等等
我们上次学过的 手势系统 又派上用场啦
通过监听手势事件配合 worklet 函数即可在小程序实现图片预览效果
👇 下面演示缩放手势的处理,除了缩放之外,相册在手势处理上还有很多复杂的逻辑,包括惯性、边界逻辑判断等 点击查看更多相册相关的手势操作
// .wxml
// 绑定缩放手势
<scale-gesture-handler onGestureEvent="onScale">
<view id="image">
<image></image>
</view>
</scale-gesture-handler>
let sharedValues = this.sharedValues ?? []
// .js
// 绑定缩放
this.applyAnimatedStyle('#image', () => {
'worklet'
// worklet 函数,sharedValues 变化时,函数会立即执行
return {
transform: `scale(${sharedValues[SCALE].value})`
}
})
// 监听缩放
onScale(evt) {
'worklet'
// 连续的手势状态 && 双指放缩
if (evt.state === GestureState.ACTIVE && evt.pointerCount === 2) {
// 计算出当前真正的缩放值
sharedValues[SCALE].value = evt.scale / sharedValues[TEMP_LAST_SCALE].value
sharedValues[TEMP_LAST_SCALE].value = evt.scale
}
}
最后,我们来看下小程序实现出来的相册跟原生相册的使用对比,在小程序也可以顺滑的实现类原生的效果啦~
目前,同程旅行 已经上线了共享元素结合手势的相册效果,mark这个 相册源码 直接接入到你的小程序吧~